@anantagnanam/lms 0.1.0 → 0.2.0
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/LICENSE +21 -21
- package/README.md +23 -2
- package/astro/client.ts +73 -151
- package/astro/components/CourseOverview.astro +25 -3
- package/astro/components/LearnerDashboard.astro +6 -0
- package/astro/components/LearningShell.astro +10 -1
- package/astro/components/LessonAvailabilityNote.astro +26 -0
- package/astro/components/LessonContent.astro +53 -0
- package/astro/components/LessonPlayer.astro +40 -61
- package/astro/components/MediaPlayer.astro +55 -4
- package/astro/media-player.ts +99 -0
- package/astro/pages/author-preview.astro +71 -0
- package/astro/pages/course.astro +10 -0
- package/astro/pages/lesson.astro +3 -0
- package/astro/pages/library.astro +173 -0
- package/astro/pages/paths.astro +73 -0
- package/astro/quiz-drafts.ts +87 -0
- package/astro/quiz-player.ts +362 -0
- package/astro/styles.css +12 -0
- package/dist/admin.d.ts +3 -1
- package/dist/admin.js +585 -116
- package/dist/admin.js.map +1 -1
- package/dist/astro.js +3 -0
- package/dist/astro.js.map +1 -1
- package/dist/{constants-C0kT94tc.js → constants-Cnx7k82Z.js} +25 -3
- package/dist/constants-Cnx7k82Z.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime.d.ts +104 -12
- package/dist/runtime.js +896 -116
- package/dist/runtime.js.map +1 -1
- package/dist/schemas.d.ts +68 -1
- package/dist/schemas.js +34 -2
- package/dist/schemas.js.map +1 -1
- package/dist/server.d.ts +11 -1
- package/dist/server.js +13 -2
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +107 -4
- package/package.json +1 -1
- package/dist/constants-C0kT94tc.js.map +0 -1
package/dist/admin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as PLUGIN_ID, t as API_BASE } from "./constants-
|
|
1
|
+
import { r as PLUGIN_ID, t as API_BASE } from "./constants-Cnx7k82Z.js";
|
|
2
2
|
import { assignmentSchema, courseDataSchema, lessonSchema, moduleSchema, questionSchema, quizSchema, settingsSchema } from "./schemas.js";
|
|
3
3
|
import { n as enrollmentCsvLines, t as enrollmentCsvHeader } from "./csv-DKq_4OWU.js";
|
|
4
4
|
import { useEffect, useState } from "react";
|
|
@@ -208,6 +208,7 @@ function PageFrame({ title, description, children }) {
|
|
|
208
208
|
["/assignments", "Assignments"],
|
|
209
209
|
["/community", "Community"],
|
|
210
210
|
["/cohorts", "Cohorts"],
|
|
211
|
+
["/paths", "Learning paths"],
|
|
211
212
|
["/reports", "Reports"],
|
|
212
213
|
["/notifications", "Notifications"],
|
|
213
214
|
["/audit", "Audit history"],
|
|
@@ -443,8 +444,48 @@ function CoursesPage() {
|
|
|
443
444
|
]
|
|
444
445
|
});
|
|
445
446
|
}
|
|
447
|
+
function CourseHistory({ courseId, disabled, restore }) {
|
|
448
|
+
const [cursor, setCursor] = useState();
|
|
449
|
+
const load = useLoad("admin/course/history", {
|
|
450
|
+
id: courseId,
|
|
451
|
+
cursor
|
|
452
|
+
});
|
|
453
|
+
return /* @__PURE__ */ jsxs("details", {
|
|
454
|
+
className: "lms-panel",
|
|
455
|
+
children: [
|
|
456
|
+
/* @__PURE__ */ jsx("summary", { children: "Saved revision history" }),
|
|
457
|
+
/* @__PURE__ */ jsx(Notice, { error: load.error }),
|
|
458
|
+
/* @__PURE__ */ jsx("p", { children: "Restore a revision into a draft, then review and publish it. Save current edits before restoring." }),
|
|
459
|
+
load.data?.items.map((item) => /* @__PURE__ */ jsxs("div", {
|
|
460
|
+
className: "lms-toolbar",
|
|
461
|
+
children: [/* @__PURE__ */ jsxs("span", { children: [
|
|
462
|
+
new Date(item.createdAt).toLocaleString(),
|
|
463
|
+
" · ",
|
|
464
|
+
item.kind,
|
|
465
|
+
" · ",
|
|
466
|
+
item.title
|
|
467
|
+
] }), /* @__PURE__ */ jsx(Button, {
|
|
468
|
+
disabled: disabled || load.loading,
|
|
469
|
+
onClick: () => restore(item.id),
|
|
470
|
+
children: "Restore as draft"
|
|
471
|
+
})]
|
|
472
|
+
}, item.id)),
|
|
473
|
+
!load.data?.items.length && /* @__PURE__ */ jsx("p", { children: "No saved history yet. Legacy courses gain history on their next save or migration." }),
|
|
474
|
+
cursor && /* @__PURE__ */ jsx(Button, {
|
|
475
|
+
onClick: () => setCursor(void 0),
|
|
476
|
+
children: "Latest revisions"
|
|
477
|
+
}),
|
|
478
|
+
load.data?.hasMore && /* @__PURE__ */ jsx(Button, {
|
|
479
|
+
disabled: load.loading,
|
|
480
|
+
onClick: () => setCursor(load.data?.cursor),
|
|
481
|
+
children: "Older revisions"
|
|
482
|
+
})
|
|
483
|
+
]
|
|
484
|
+
});
|
|
485
|
+
}
|
|
446
486
|
function CourseEditor({ initial, initialRevision, onClose }) {
|
|
447
487
|
const [course, setCourse] = useState(initial);
|
|
488
|
+
const academy = useLoad("academy");
|
|
448
489
|
const [revision, setRevision] = useState(initialRevision);
|
|
449
490
|
const [saved, setSaved] = useState(JSON.stringify(initial));
|
|
450
491
|
const [selected, setSelected] = useState(initial.modules[0]?.lessons[0]?.id ?? null);
|
|
@@ -483,21 +524,29 @@ function CourseEditor({ initial, initialRevision, onClose }) {
|
|
|
483
524
|
}))
|
|
484
525
|
}));
|
|
485
526
|
const selectedLesson = course.modules.flatMap((module) => module.lessons).find((lesson) => lesson.id === selected);
|
|
486
|
-
const
|
|
527
|
+
const acceptSaved = (result) => {
|
|
528
|
+
setCourse(result.course);
|
|
529
|
+
setRevision(result.revision);
|
|
530
|
+
setSaved(JSON.stringify(result.course));
|
|
531
|
+
};
|
|
532
|
+
const publishOrArchive = (operation) => void action.run(async () => {
|
|
533
|
+
acceptSaved(await api(`admin/course/${operation}`, {
|
|
534
|
+
id: course.id,
|
|
535
|
+
expectedRevision: revision
|
|
536
|
+
}, "POST"));
|
|
537
|
+
}, operation === "publish" ? "Saved draft published. Existing learners keep their enrolled edition." : "Course archived.");
|
|
538
|
+
const save = () => void action.run(async () => {
|
|
487
539
|
const parsed = courseDataSchema.safeParse({
|
|
488
540
|
...course,
|
|
489
|
-
status
|
|
541
|
+
status: "draft"
|
|
490
542
|
});
|
|
491
543
|
if (!parsed.success) throw new Error(parsed.error.issues.slice(0, 5).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(" "));
|
|
492
|
-
|
|
544
|
+
acceptSaved(await api("admin/course/save", {
|
|
493
545
|
id: course.id,
|
|
494
546
|
expectedRevision: revision,
|
|
495
547
|
data: parsed.data
|
|
496
|
-
}, "POST");
|
|
497
|
-
|
|
498
|
-
setRevision(result.revision);
|
|
499
|
-
setSaved(JSON.stringify(result.course));
|
|
500
|
-
}, status === "published" ? "Course published." : "Course saved.");
|
|
548
|
+
}, "POST"));
|
|
549
|
+
}, "Draft saved. Publish it when it is ready for new learners.");
|
|
501
550
|
const reorderLesson = (moduleIndex, index, delta) => {
|
|
502
551
|
const items = [...course.modules[moduleIndex].lessons];
|
|
503
552
|
const to = index + delta;
|
|
@@ -522,19 +571,37 @@ function CourseEditor({ initial, initialRevision, onClose }) {
|
|
|
522
571
|
},
|
|
523
572
|
children: "Back to courses"
|
|
524
573
|
}),
|
|
525
|
-
/* @__PURE__ */
|
|
574
|
+
/* @__PURE__ */ jsx(Button, {
|
|
526
575
|
busy: action.busy,
|
|
527
576
|
onClick: () => save(),
|
|
528
|
-
children:
|
|
577
|
+
children: "Save draft"
|
|
529
578
|
}),
|
|
530
|
-
course.
|
|
579
|
+
course.id && /* @__PURE__ */ jsx(Button, {
|
|
531
580
|
busy: action.busy,
|
|
532
|
-
|
|
533
|
-
|
|
581
|
+
disabled: dirty,
|
|
582
|
+
onClick: () => void action.run(async () => {
|
|
583
|
+
const exported = await api("admin/course/export", { id: course.id });
|
|
584
|
+
acceptSaved(await api("admin/course/import", {
|
|
585
|
+
...exported,
|
|
586
|
+
course: {
|
|
587
|
+
...exported.course,
|
|
588
|
+
title: `Copy of ${course.title}`.slice(0, 200),
|
|
589
|
+
slug: `${course.slug.slice(0, 65)}-copy-${crypto.randomUUID().slice(0, 8)}`
|
|
590
|
+
}
|
|
591
|
+
}, "POST"));
|
|
592
|
+
}, "Course duplicated as a new draft. Review its dates and access settings before publishing."),
|
|
593
|
+
children: "Duplicate as draft"
|
|
594
|
+
}),
|
|
595
|
+
/* @__PURE__ */ jsx(Button, {
|
|
596
|
+
busy: action.busy,
|
|
597
|
+
disabled: dirty,
|
|
598
|
+
onClick: () => publishOrArchive("publish"),
|
|
599
|
+
children: "Publish saved draft"
|
|
534
600
|
}),
|
|
535
601
|
course.status === "published" && /* @__PURE__ */ jsx(Button, {
|
|
536
602
|
busy: action.busy,
|
|
537
|
-
|
|
603
|
+
disabled: dirty,
|
|
604
|
+
onClick: () => publishOrArchive("archive"),
|
|
538
605
|
children: "Archive"
|
|
539
606
|
}),
|
|
540
607
|
course.id && course.status === "draft" && /* @__PURE__ */ jsx(Button, {
|
|
@@ -553,6 +620,29 @@ function CourseEditor({ initial, initialRevision, onClose }) {
|
|
|
553
620
|
})
|
|
554
621
|
]
|
|
555
622
|
}),
|
|
623
|
+
/* @__PURE__ */ jsxs("p", {
|
|
624
|
+
role: "status",
|
|
625
|
+
children: [
|
|
626
|
+
"Course visibility: ",
|
|
627
|
+
course.status,
|
|
628
|
+
".",
|
|
629
|
+
dirty ? " Save changes before publishing." : course.publishedEditionId && course.draftRevisionId !== course.publishedDraftRevisionId ? " Saved draft changes are awaiting publication." : "",
|
|
630
|
+
course.publishedEditionId ? " Publishing creates a new edition for new enrollments; existing learners keep their course material and progress." : ""
|
|
631
|
+
]
|
|
632
|
+
}),
|
|
633
|
+
course.id && /* @__PURE__ */ jsx(CourseHistory, {
|
|
634
|
+
courseId: course.id,
|
|
635
|
+
disabled: dirty || action.busy,
|
|
636
|
+
restore: (revisionId) => void action.run(async () => {
|
|
637
|
+
const result = await api("admin/course/restore", {
|
|
638
|
+
id: course.id,
|
|
639
|
+
expectedRevision: revision,
|
|
640
|
+
revisionId
|
|
641
|
+
}, "POST");
|
|
642
|
+
acceptSaved(result);
|
|
643
|
+
setSelected(result.course.modules[0]?.lessons[0]?.id ?? null);
|
|
644
|
+
}, "Revision restored into a saved draft. Review it before publishing.")
|
|
645
|
+
}, revision),
|
|
556
646
|
/* @__PURE__ */ jsx("div", {
|
|
557
647
|
className: "lms-tabs",
|
|
558
648
|
role: "tablist",
|
|
@@ -887,10 +977,44 @@ function CourseEditor({ initial, initialRevision, onClose }) {
|
|
|
887
977
|
children: "Add module"
|
|
888
978
|
})
|
|
889
979
|
]
|
|
890
|
-
}), /* @__PURE__ */
|
|
980
|
+
}), /* @__PURE__ */ jsxs(Panel, {
|
|
891
981
|
title: selectedLesson?.title ?? "Choose a lesson",
|
|
892
|
-
children: selectedLesson
|
|
982
|
+
children: [selectedLesson && /* @__PURE__ */ jsxs("div", {
|
|
983
|
+
className: "lms-actions",
|
|
984
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
985
|
+
onClick: () => {
|
|
986
|
+
const copy = {
|
|
987
|
+
...structuredClone(selectedLesson),
|
|
988
|
+
id: crypto.randomUUID(),
|
|
989
|
+
title: `Copy of ${selectedLesson.title}`.slice(0, 200)
|
|
990
|
+
};
|
|
991
|
+
patch({ modules: course.modules.map((module) => ({
|
|
992
|
+
...module,
|
|
993
|
+
lessons: module.lessons.flatMap((lesson) => lesson.id === selectedLesson.id ? [lesson, copy] : [lesson])
|
|
994
|
+
})) });
|
|
995
|
+
setSelected(copy.id);
|
|
996
|
+
},
|
|
997
|
+
children: "Duplicate lesson"
|
|
998
|
+
}), /* @__PURE__ */ jsx(Field, {
|
|
999
|
+
label: "Move lesson to module",
|
|
1000
|
+
children: /* @__PURE__ */ jsx("select", {
|
|
1001
|
+
value: course.modules.find((m) => m.lessons.some((l) => l.id === selectedLesson.id))?.id,
|
|
1002
|
+
onChange: (event) => {
|
|
1003
|
+
const target = event.target.value;
|
|
1004
|
+
patch({ modules: course.modules.map((module) => ({
|
|
1005
|
+
...module,
|
|
1006
|
+
lessons: module.id === target ? [...module.lessons, selectedLesson] : module.lessons.filter((lesson) => lesson.id !== selectedLesson.id)
|
|
1007
|
+
})) });
|
|
1008
|
+
},
|
|
1009
|
+
children: course.modules.map((module) => /* @__PURE__ */ jsx("option", {
|
|
1010
|
+
value: module.id,
|
|
1011
|
+
children: module.title
|
|
1012
|
+
}, module.id))
|
|
1013
|
+
})
|
|
1014
|
+
})]
|
|
1015
|
+
}), selectedLesson ? /* @__PURE__ */ jsx(LessonEditor, {
|
|
893
1016
|
lesson: selectedLesson,
|
|
1017
|
+
previewUrl: course.id && academy.data && JSON.parse(saved).modules.some((module) => module.lessons.some((lesson) => lesson.id === selectedLesson.id)) ? `${academy.data.basePath}/teach/courses/${encodeURIComponent(course.id)}/lessons/${encodeURIComponent(selectedLesson.id)}` : void 0,
|
|
894
1018
|
update: (data) => updateLesson(selectedLesson.id, data),
|
|
895
1019
|
remove: () => {
|
|
896
1020
|
if (window.confirm(`Remove “${selectedLesson.title}” from the draft?`)) {
|
|
@@ -901,7 +1025,7 @@ function CourseEditor({ initial, initialRevision, onClose }) {
|
|
|
901
1025
|
setSelected(null);
|
|
902
1026
|
}
|
|
903
1027
|
}
|
|
904
|
-
}) : /* @__PURE__ */ jsx("p", { children: "Add a module and a lesson to begin." })
|
|
1028
|
+
}, selectedLesson.id) : /* @__PURE__ */ jsx("p", { children: "Add a module and a lesson to begin." })]
|
|
905
1029
|
})]
|
|
906
1030
|
})
|
|
907
1031
|
]
|
|
@@ -935,11 +1059,10 @@ function ContentPicker({ value, onChange }) {
|
|
|
935
1059
|
setCollection(event.target.value);
|
|
936
1060
|
setCursor(void 0);
|
|
937
1061
|
setEntries([]);
|
|
938
|
-
onChange(null);
|
|
939
1062
|
},
|
|
940
1063
|
children: [/* @__PURE__ */ jsx("option", {
|
|
941
1064
|
value: "",
|
|
942
|
-
children: "
|
|
1065
|
+
children: "Choose a collection"
|
|
943
1066
|
}), schema.data?.map((entry) => /* @__PURE__ */ jsx("option", {
|
|
944
1067
|
value: entry.slug,
|
|
945
1068
|
children: entry.label
|
|
@@ -948,18 +1071,18 @@ function ContentPicker({ value, onChange }) {
|
|
|
948
1071
|
}), collection && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Field, {
|
|
949
1072
|
label: "Published entry",
|
|
950
1073
|
children: /* @__PURE__ */ jsxs("select", {
|
|
951
|
-
value: value?.id
|
|
952
|
-
onChange: (event) => onChange(event.target.value ? {
|
|
1074
|
+
value: value?.collection === collection ? value.id : "",
|
|
1075
|
+
onChange: (event) => event.target.value && onChange(event.target.value ? {
|
|
953
1076
|
collection,
|
|
954
1077
|
id: event.target.value,
|
|
955
|
-
field: value?.field ?? fields.find((field) => field.type === "portableText")?.slug ?? fields[0]?.slug ?? "content"
|
|
1078
|
+
field: (value?.collection === collection ? value.field : void 0) ?? fields.find((field) => field.type === "portableText")?.slug ?? fields[0]?.slug ?? "content"
|
|
956
1079
|
} : null),
|
|
957
1080
|
children: [
|
|
958
1081
|
/* @__PURE__ */ jsx("option", {
|
|
959
1082
|
value: "",
|
|
960
1083
|
children: "Choose an entry"
|
|
961
1084
|
}),
|
|
962
|
-
value && !entries.some((entry) => entry.id === value.id) && /* @__PURE__ */ jsx("option", {
|
|
1085
|
+
value?.collection === collection && !entries.some((entry) => entry.id === value.id) && /* @__PURE__ */ jsx("option", {
|
|
963
1086
|
value: value.id,
|
|
964
1087
|
children: "Current linked entry"
|
|
965
1088
|
}),
|
|
@@ -972,8 +1095,8 @@ function ContentPicker({ value, onChange }) {
|
|
|
972
1095
|
}), /* @__PURE__ */ jsx(Field, {
|
|
973
1096
|
label: "Content field",
|
|
974
1097
|
children: /* @__PURE__ */ jsxs("select", {
|
|
975
|
-
value: value?.field
|
|
976
|
-
disabled: !value,
|
|
1098
|
+
value: value?.collection === collection ? value.field : "",
|
|
1099
|
+
disabled: !value || value.collection !== collection,
|
|
977
1100
|
onChange: (event) => value && onChange({
|
|
978
1101
|
...value,
|
|
979
1102
|
field: event.target.value
|
|
@@ -994,7 +1117,7 @@ function ContentPicker({ value, onChange }) {
|
|
|
994
1117
|
})
|
|
995
1118
|
] });
|
|
996
1119
|
}
|
|
997
|
-
function LessonEditor({ lesson, update, remove }) {
|
|
1120
|
+
function LessonEditor({ lesson, previewUrl, update, remove }) {
|
|
998
1121
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
999
1122
|
/* @__PURE__ */ jsxs("div", {
|
|
1000
1123
|
className: "lms-grid",
|
|
@@ -1061,7 +1184,58 @@ function LessonEditor({ lesson, update, remove }) {
|
|
|
1061
1184
|
onChange: (event) => update({ summary: event.target.value })
|
|
1062
1185
|
})
|
|
1063
1186
|
}),
|
|
1064
|
-
/* @__PURE__ */
|
|
1187
|
+
/* @__PURE__ */ jsxs("section", {
|
|
1188
|
+
className: "lms-notice",
|
|
1189
|
+
children: [
|
|
1190
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
1191
|
+
"Content source:",
|
|
1192
|
+
" ",
|
|
1193
|
+
lesson.contentSource ? "Published CMS content" : lesson.portableText.length ? "Imported rich content" : "Inline text"
|
|
1194
|
+
] }),
|
|
1195
|
+
previewUrl ? /* @__PURE__ */ jsxs("p", { children: [
|
|
1196
|
+
/* @__PURE__ */ jsx("a", {
|
|
1197
|
+
href: previewUrl,
|
|
1198
|
+
target: "_blank",
|
|
1199
|
+
rel: "noopener noreferrer",
|
|
1200
|
+
children: "Preview saved lesson ↗"
|
|
1201
|
+
}),
|
|
1202
|
+
" ",
|
|
1203
|
+
"· Save changes first to include them in this preview."
|
|
1204
|
+
] }) : /* @__PURE__ */ jsx("p", { children: "Save this lesson to enable its preview." }),
|
|
1205
|
+
lesson.contentSource ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1206
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
1207
|
+
"This lesson displays the published “",
|
|
1208
|
+
lesson.contentSource.field,
|
|
1209
|
+
"” field from",
|
|
1210
|
+
" ",
|
|
1211
|
+
lesson.contentSource.collection,
|
|
1212
|
+
". Local content is retained while linked."
|
|
1213
|
+
] }),
|
|
1214
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
1215
|
+
/* @__PURE__ */ jsx("a", {
|
|
1216
|
+
href: `/_emdash/admin/content/${encodeURIComponent(lesson.contentSource.collection)}/${encodeURIComponent(lesson.contentSource.id)}`,
|
|
1217
|
+
target: "_blank",
|
|
1218
|
+
rel: "noopener noreferrer",
|
|
1219
|
+
children: "Edit linked entry in EmDash ↗"
|
|
1220
|
+
}),
|
|
1221
|
+
" ",
|
|
1222
|
+
"· Publish entry changes, then publish the saved course draft to create a new learner edition."
|
|
1223
|
+
] }),
|
|
1224
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1225
|
+
onClick: () => {
|
|
1226
|
+
if (window.confirm("Unlink the CMS entry and use this lesson's retained local content? Save the course to apply.")) update({ contentSource: null });
|
|
1227
|
+
},
|
|
1228
|
+
children: "Use retained local content"
|
|
1229
|
+
})
|
|
1230
|
+
] }) : lesson.portableText.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("p", { children: "Imported rich content is read-only here. Use the saved preview to inspect it, or link a published CMS entry below to edit rich content in EmDash." }), /* @__PURE__ */ jsx(Button, {
|
|
1231
|
+
onClick: () => {
|
|
1232
|
+
if (window.confirm("Remove this lesson's imported rich content and use its retained plain text instead? This removes formatting when you save.")) update({ portableText: [] });
|
|
1233
|
+
},
|
|
1234
|
+
children: "Replace rich content with inline text"
|
|
1235
|
+
})] }) : /* @__PURE__ */ jsx("p", { children: "Edit the inline text below, or select a published CMS entry." })
|
|
1236
|
+
]
|
|
1237
|
+
}),
|
|
1238
|
+
!lesson.contentSource && lesson.portableText.length === 0 && /* @__PURE__ */ jsx(Field, {
|
|
1065
1239
|
label: "Lesson text",
|
|
1066
1240
|
children: /* @__PURE__ */ jsx("textarea", {
|
|
1067
1241
|
rows: 12,
|
|
@@ -1090,6 +1264,65 @@ function LessonEditor({ lesson, update, remove }) {
|
|
|
1090
1264
|
onChange: (event) => update({ liveStartsAt: isoDate(event.target.value) })
|
|
1091
1265
|
})
|
|
1092
1266
|
}),
|
|
1267
|
+
["video", "audio"].includes(lesson.kind) && /* @__PURE__ */ jsxs("section", { children: [
|
|
1268
|
+
/* @__PURE__ */ jsx(Field, {
|
|
1269
|
+
label: "Transcript",
|
|
1270
|
+
help: "Readable text shown alongside native and embedded media.",
|
|
1271
|
+
children: /* @__PURE__ */ jsx("textarea", {
|
|
1272
|
+
rows: 6,
|
|
1273
|
+
maxLength: 5e4,
|
|
1274
|
+
value: lesson.transcript ?? "",
|
|
1275
|
+
onChange: (event) => update({ transcript: event.target.value })
|
|
1276
|
+
})
|
|
1277
|
+
}),
|
|
1278
|
+
/* @__PURE__ */ jsx("h3", { children: "Caption tracks" }),
|
|
1279
|
+
/* @__PURE__ */ jsx("p", { children: "WebVTT tracks apply to native media. Use same-origin files or configure CORS on both media and caption hosts. Embedded players manage their own captions." }),
|
|
1280
|
+
(lesson.captions ?? []).map((track, index) => /* @__PURE__ */ jsxs("div", { children: [
|
|
1281
|
+
/* @__PURE__ */ jsx(Field, {
|
|
1282
|
+
label: `Track ${index + 1} URL`,
|
|
1283
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
1284
|
+
value: track.src,
|
|
1285
|
+
onChange: (event) => update({ captions: lesson.captions.map((item, i) => i === index ? {
|
|
1286
|
+
...item,
|
|
1287
|
+
src: event.target.value
|
|
1288
|
+
} : item) })
|
|
1289
|
+
})
|
|
1290
|
+
}),
|
|
1291
|
+
/* @__PURE__ */ jsx(Field, {
|
|
1292
|
+
label: "Language code",
|
|
1293
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
1294
|
+
value: track.language,
|
|
1295
|
+
onChange: (event) => update({ captions: lesson.captions.map((item, i) => i === index ? {
|
|
1296
|
+
...item,
|
|
1297
|
+
language: event.target.value
|
|
1298
|
+
} : item) })
|
|
1299
|
+
})
|
|
1300
|
+
}),
|
|
1301
|
+
/* @__PURE__ */ jsx(Field, {
|
|
1302
|
+
label: "Caption label",
|
|
1303
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
1304
|
+
value: track.label,
|
|
1305
|
+
onChange: (event) => update({ captions: lesson.captions.map((item, i) => i === index ? {
|
|
1306
|
+
...item,
|
|
1307
|
+
label: event.target.value
|
|
1308
|
+
} : item) })
|
|
1309
|
+
})
|
|
1310
|
+
}),
|
|
1311
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1312
|
+
onClick: () => update({ captions: lesson.captions.filter((_, i) => i !== index) }),
|
|
1313
|
+
children: "Remove track"
|
|
1314
|
+
})
|
|
1315
|
+
] }, index)),
|
|
1316
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1317
|
+
disabled: (lesson.captions?.length ?? 0) >= 20,
|
|
1318
|
+
onClick: () => update({ captions: [...lesson.captions ?? [], {
|
|
1319
|
+
src: "",
|
|
1320
|
+
language: "en",
|
|
1321
|
+
label: "English"
|
|
1322
|
+
}] }),
|
|
1323
|
+
children: "Add captions"
|
|
1324
|
+
})
|
|
1325
|
+
] }),
|
|
1093
1326
|
/* @__PURE__ */ jsxs("div", {
|
|
1094
1327
|
className: "lms-actions",
|
|
1095
1328
|
children: [/* @__PURE__ */ jsxs("label", {
|
|
@@ -1114,7 +1347,9 @@ function LessonEditor({ lesson, update, remove }) {
|
|
|
1114
1347
|
}),
|
|
1115
1348
|
/* @__PURE__ */ jsx(ContentPicker, {
|
|
1116
1349
|
value: lesson.contentSource,
|
|
1117
|
-
onChange: (contentSource) =>
|
|
1350
|
+
onChange: (contentSource) => {
|
|
1351
|
+
if (contentSource && window.confirm("Use this published CMS field as the lesson content? Local content will be retained. Save the course to apply.")) update({ contentSource });
|
|
1352
|
+
}
|
|
1118
1353
|
}, lesson.id),
|
|
1119
1354
|
lesson.kind === "quiz" && lesson.quiz && /* @__PURE__ */ jsx(QuizEditor, {
|
|
1120
1355
|
quiz: lesson.quiz,
|
|
@@ -2199,6 +2434,165 @@ function CohortMembershipQueue() {
|
|
|
2199
2434
|
]
|
|
2200
2435
|
});
|
|
2201
2436
|
}
|
|
2437
|
+
function PathsPage() {
|
|
2438
|
+
const [cursor, setCursor] = useState();
|
|
2439
|
+
const list = useLoad("admin/paths", { cursor });
|
|
2440
|
+
const courses = useChoices("admin/courses");
|
|
2441
|
+
const [edit, setEdit] = useState();
|
|
2442
|
+
const action = useAction();
|
|
2443
|
+
return /* @__PURE__ */ jsxs(PageFrame, {
|
|
2444
|
+
title: "Learning paths",
|
|
2445
|
+
description: "Build ordered programs. Learners retain the course list they joined; individual course enrollment still controls access.",
|
|
2446
|
+
children: [
|
|
2447
|
+
/* @__PURE__ */ jsx(Notice, {
|
|
2448
|
+
error: list.error || courses.error || action.error,
|
|
2449
|
+
message: action.message
|
|
2450
|
+
}),
|
|
2451
|
+
!edit && /* @__PURE__ */ jsx(Button, {
|
|
2452
|
+
onClick: () => setEdit({
|
|
2453
|
+
revision: null,
|
|
2454
|
+
title: "New learning path",
|
|
2455
|
+
description: "",
|
|
2456
|
+
status: "draft",
|
|
2457
|
+
courseIds: []
|
|
2458
|
+
}),
|
|
2459
|
+
children: "Create learning path"
|
|
2460
|
+
}),
|
|
2461
|
+
edit && /* @__PURE__ */ jsx(Panel, {
|
|
2462
|
+
title: edit.id ? "Edit path" : "Create path",
|
|
2463
|
+
children: /* @__PURE__ */ jsxs("form", {
|
|
2464
|
+
onSubmit: (event) => {
|
|
2465
|
+
event.preventDefault();
|
|
2466
|
+
action.run(async () => {
|
|
2467
|
+
const { id, revision, ...data } = edit;
|
|
2468
|
+
await api("admin/path/save", {
|
|
2469
|
+
id,
|
|
2470
|
+
expectedRevision: revision,
|
|
2471
|
+
data
|
|
2472
|
+
}, "POST");
|
|
2473
|
+
setEdit(void 0);
|
|
2474
|
+
list.reload();
|
|
2475
|
+
}, "Path saved. Existing learners retain their joined course lists.");
|
|
2476
|
+
},
|
|
2477
|
+
children: [
|
|
2478
|
+
/* @__PURE__ */ jsx(Field, {
|
|
2479
|
+
label: "Title",
|
|
2480
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
2481
|
+
required: true,
|
|
2482
|
+
maxLength: 200,
|
|
2483
|
+
value: edit.title,
|
|
2484
|
+
onChange: (event) => setEdit({
|
|
2485
|
+
...edit,
|
|
2486
|
+
title: event.target.value
|
|
2487
|
+
})
|
|
2488
|
+
})
|
|
2489
|
+
}),
|
|
2490
|
+
/* @__PURE__ */ jsx(Field, {
|
|
2491
|
+
label: "Description",
|
|
2492
|
+
children: /* @__PURE__ */ jsx("textarea", {
|
|
2493
|
+
maxLength: 5e3,
|
|
2494
|
+
rows: 4,
|
|
2495
|
+
value: edit.description,
|
|
2496
|
+
onChange: (event) => setEdit({
|
|
2497
|
+
...edit,
|
|
2498
|
+
description: event.target.value
|
|
2499
|
+
})
|
|
2500
|
+
})
|
|
2501
|
+
}),
|
|
2502
|
+
/* @__PURE__ */ jsx(Field, {
|
|
2503
|
+
label: "Visibility",
|
|
2504
|
+
children: /* @__PURE__ */ jsxs("select", {
|
|
2505
|
+
value: edit.status,
|
|
2506
|
+
onChange: (event) => setEdit({
|
|
2507
|
+
...edit,
|
|
2508
|
+
status: event.target.value
|
|
2509
|
+
}),
|
|
2510
|
+
children: [
|
|
2511
|
+
/* @__PURE__ */ jsx("option", {
|
|
2512
|
+
value: "draft",
|
|
2513
|
+
children: "Draft"
|
|
2514
|
+
}),
|
|
2515
|
+
/* @__PURE__ */ jsx("option", {
|
|
2516
|
+
value: "published",
|
|
2517
|
+
children: "Published"
|
|
2518
|
+
}),
|
|
2519
|
+
/* @__PURE__ */ jsx("option", {
|
|
2520
|
+
value: "archived",
|
|
2521
|
+
children: "Archived"
|
|
2522
|
+
})
|
|
2523
|
+
]
|
|
2524
|
+
})
|
|
2525
|
+
}),
|
|
2526
|
+
/* @__PURE__ */ jsx(ChoicesField, {
|
|
2527
|
+
label: "Courses",
|
|
2528
|
+
choices: courses.items.map((course) => ({
|
|
2529
|
+
id: course.id,
|
|
2530
|
+
label: `${course.title} (${course.status})`
|
|
2531
|
+
})),
|
|
2532
|
+
value: edit.courseIds,
|
|
2533
|
+
onChange: (courseIds) => setEdit({
|
|
2534
|
+
...edit,
|
|
2535
|
+
courseIds
|
|
2536
|
+
})
|
|
2537
|
+
}),
|
|
2538
|
+
courses.data?.hasMore && /* @__PURE__ */ jsx(Button, {
|
|
2539
|
+
onClick: courses.more,
|
|
2540
|
+
children: "Load more courses"
|
|
2541
|
+
}),
|
|
2542
|
+
/* @__PURE__ */ jsx("ol", { children: edit.courseIds.map((id, index) => /* @__PURE__ */ jsxs("li", { children: [courses.items.find((course) => course.id === id)?.title ?? id, /* @__PURE__ */ jsx(Button, {
|
|
2543
|
+
disabled: index === 0,
|
|
2544
|
+
"aria-label": "Move course earlier",
|
|
2545
|
+
onClick: () => {
|
|
2546
|
+
const ids = [...edit.courseIds];
|
|
2547
|
+
[ids[index], ids[index - 1]] = [ids[index - 1], ids[index]];
|
|
2548
|
+
setEdit({
|
|
2549
|
+
...edit,
|
|
2550
|
+
courseIds: ids
|
|
2551
|
+
});
|
|
2552
|
+
},
|
|
2553
|
+
children: "Move up"
|
|
2554
|
+
})] }, id)) }),
|
|
2555
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2556
|
+
type: "submit",
|
|
2557
|
+
busy: action.busy,
|
|
2558
|
+
children: "Save path"
|
|
2559
|
+
}),
|
|
2560
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2561
|
+
disabled: action.busy,
|
|
2562
|
+
onClick: () => setEdit(void 0),
|
|
2563
|
+
children: "Cancel"
|
|
2564
|
+
})
|
|
2565
|
+
]
|
|
2566
|
+
})
|
|
2567
|
+
}),
|
|
2568
|
+
list.data?.items.map((path) => /* @__PURE__ */ jsxs(Panel, {
|
|
2569
|
+
title: path.title,
|
|
2570
|
+
children: [/* @__PURE__ */ jsxs("p", { children: [
|
|
2571
|
+
path.status,
|
|
2572
|
+
" · ",
|
|
2573
|
+
path.courseIds.length,
|
|
2574
|
+
" courses"
|
|
2575
|
+
] }), /* @__PURE__ */ jsx(Button, {
|
|
2576
|
+
disabled: Boolean(edit),
|
|
2577
|
+
onClick: () => setEdit({
|
|
2578
|
+
id: path.id,
|
|
2579
|
+
revision: path.revision,
|
|
2580
|
+
title: path.title,
|
|
2581
|
+
description: path.description,
|
|
2582
|
+
courseIds: path.courseIds,
|
|
2583
|
+
status: path.status
|
|
2584
|
+
}),
|
|
2585
|
+
children: "Edit path"
|
|
2586
|
+
})]
|
|
2587
|
+
}, path.id)),
|
|
2588
|
+
/* @__PURE__ */ jsx(Pager, {
|
|
2589
|
+
page: list.data,
|
|
2590
|
+
next: setCursor,
|
|
2591
|
+
reset: () => setCursor(void 0)
|
|
2592
|
+
})
|
|
2593
|
+
]
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2202
2596
|
function CohortsPage() {
|
|
2203
2597
|
const [cursor, setCursor] = useState();
|
|
2204
2598
|
const list = useLoad("admin/cohorts", { cursor });
|
|
@@ -2606,6 +3000,75 @@ function AuditPage() {
|
|
|
2606
3000
|
]
|
|
2607
3001
|
});
|
|
2608
3002
|
}
|
|
3003
|
+
function MigrationPanel() {
|
|
3004
|
+
const load = useLoad("admin/migrations");
|
|
3005
|
+
const action = useAction();
|
|
3006
|
+
return /* @__PURE__ */ jsxs(Panel, { children: [
|
|
3007
|
+
/* @__PURE__ */ jsx("h2", { children: "Course edition migration" }),
|
|
3008
|
+
/* @__PURE__ */ jsx("p", { children: "Upgrade existing courses and pin enrollments to their original curriculum. Progress, assessments, grants and certificates are retained. Each batch handles up to 20 records and can be resumed after interruption." }),
|
|
3009
|
+
/* @__PURE__ */ jsx(Notice, { error: action.error || load.error || load.data?.lastError || void 0 }),
|
|
3010
|
+
load.data && /* @__PURE__ */ jsxs("p", { children: [
|
|
3011
|
+
"Phase: ",
|
|
3012
|
+
load.data.phase,
|
|
3013
|
+
" · Courses processed: ",
|
|
3014
|
+
load.data.coursesProcessed,
|
|
3015
|
+
" · Enrollments processed: ",
|
|
3016
|
+
load.data.enrollmentsProcessed
|
|
3017
|
+
] }),
|
|
3018
|
+
/* @__PURE__ */ jsx(Button, {
|
|
3019
|
+
busy: action.busy,
|
|
3020
|
+
disabled: load.loading || load.data?.phase === "complete",
|
|
3021
|
+
onClick: () => void action.run(async () => {
|
|
3022
|
+
try {
|
|
3023
|
+
await api("admin/migrations/run", {}, "POST");
|
|
3024
|
+
} finally {
|
|
3025
|
+
load.reload();
|
|
3026
|
+
}
|
|
3027
|
+
}, "Migration checkpoint saved."),
|
|
3028
|
+
children: "Run next batch"
|
|
3029
|
+
}),
|
|
3030
|
+
/* @__PURE__ */ jsx(Button, {
|
|
3031
|
+
onClick: load.reload,
|
|
3032
|
+
children: "Refresh status"
|
|
3033
|
+
}),
|
|
3034
|
+
/* @__PURE__ */ jsx(Notice, { message: action.message })
|
|
3035
|
+
] });
|
|
3036
|
+
}
|
|
3037
|
+
function HealthPanel() {
|
|
3038
|
+
const load = useLoad("admin/health");
|
|
3039
|
+
return /* @__PURE__ */ jsxs(Panel, {
|
|
3040
|
+
title: "Operational health",
|
|
3041
|
+
children: [
|
|
3042
|
+
/* @__PURE__ */ jsx(Notice, { error: load.error }),
|
|
3043
|
+
load.data && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3044
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
3045
|
+
"Plugin ",
|
|
3046
|
+
load.data.version,
|
|
3047
|
+
" · Email ",
|
|
3048
|
+
load.data.emailEnabled ? "enabled" : "disabled"
|
|
3049
|
+
] }),
|
|
3050
|
+
/* @__PURE__ */ jsx("p", {
|
|
3051
|
+
role: load.data.maintenanceStale ? "alert" : void 0,
|
|
3052
|
+
children: load.data.maintenanceStale ? "Maintenance has not completed successfully in the last 45 minutes. Check the host scheduler and logs." : "Scheduled maintenance is reporting successfully."
|
|
3053
|
+
}),
|
|
3054
|
+
/* @__PURE__ */ jsxs("p", { children: ["Last completed: ", load.data.maintenance?.completedAt ?? "No successful run reported"] }),
|
|
3055
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
3056
|
+
"Pending emails: ",
|
|
3057
|
+
load.data.pendingEmails,
|
|
3058
|
+
" · Failed emails: ",
|
|
3059
|
+
load.data.failedEmails,
|
|
3060
|
+
" · Oldest pending: ",
|
|
3061
|
+
load.data.oldestPendingAt ?? "None"
|
|
3062
|
+
] }),
|
|
3063
|
+
/* @__PURE__ */ jsxs("p", { children: ["Migration phase: ", load.data.migration?.phase ?? "Not started"] })
|
|
3064
|
+
] }),
|
|
3065
|
+
/* @__PURE__ */ jsx(Button, {
|
|
3066
|
+
onClick: load.reload,
|
|
3067
|
+
children: "Refresh health"
|
|
3068
|
+
})
|
|
3069
|
+
]
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
2609
3072
|
function SettingsPage() {
|
|
2610
3073
|
const load = useLoad("admin/settings");
|
|
2611
3074
|
const [settings, setSettings] = useState(settingsSchema.parse({}));
|
|
@@ -2623,97 +3086,102 @@ function SettingsPage() {
|
|
|
2623
3086
|
return /* @__PURE__ */ jsxs(PageFrame, {
|
|
2624
3087
|
title: "LMS settings",
|
|
2625
3088
|
description: "Configure your academy, certificates, moderation, and reminders.",
|
|
2626
|
-
children: [
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
3089
|
+
children: [
|
|
3090
|
+
/* @__PURE__ */ jsx(Notice, {
|
|
3091
|
+
error: load.error || action.error,
|
|
3092
|
+
message: action.message
|
|
3093
|
+
}),
|
|
3094
|
+
/* @__PURE__ */ jsx(MigrationPanel, {}),
|
|
3095
|
+
/* @__PURE__ */ jsx(HealthPanel, {}),
|
|
3096
|
+
/* @__PURE__ */ jsx(Panel, { children: /* @__PURE__ */ jsxs("form", {
|
|
3097
|
+
onSubmit: save,
|
|
3098
|
+
children: [
|
|
3099
|
+
/* @__PURE__ */ jsx(Field, {
|
|
3100
|
+
label: "Academy title",
|
|
3101
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
3102
|
+
value: settings.academyTitle,
|
|
3103
|
+
onChange: (event) => setSettings({
|
|
3104
|
+
...settings,
|
|
3105
|
+
academyTitle: event.target.value
|
|
3106
|
+
})
|
|
2639
3107
|
})
|
|
2640
|
-
})
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
3108
|
+
}),
|
|
3109
|
+
/* @__PURE__ */ jsx(Field, {
|
|
3110
|
+
label: "Academy introduction",
|
|
3111
|
+
children: /* @__PURE__ */ jsx("textarea", {
|
|
3112
|
+
rows: 3,
|
|
3113
|
+
value: settings.academyDescription,
|
|
3114
|
+
onChange: (event) => setSettings({
|
|
3115
|
+
...settings,
|
|
3116
|
+
academyDescription: event.target.value
|
|
3117
|
+
})
|
|
2650
3118
|
})
|
|
2651
|
-
})
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
3119
|
+
}),
|
|
3120
|
+
/* @__PURE__ */ jsx(Field, {
|
|
3121
|
+
label: "Certificate issuer",
|
|
3122
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
3123
|
+
value: settings.issuerName,
|
|
3124
|
+
onChange: (event) => setSettings({
|
|
3125
|
+
...settings,
|
|
3126
|
+
issuerName: event.target.value
|
|
3127
|
+
})
|
|
2660
3128
|
})
|
|
2661
|
-
})
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
3129
|
+
}),
|
|
3130
|
+
/* @__PURE__ */ jsx(Field, {
|
|
3131
|
+
label: "Remind inactive learners after days",
|
|
3132
|
+
help: "0 disables reminders. Scheduled maintenance must be enabled in the host.",
|
|
3133
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
3134
|
+
type: "number",
|
|
3135
|
+
min: 0,
|
|
3136
|
+
max: 90,
|
|
3137
|
+
value: settings.reminderDays,
|
|
3138
|
+
onChange: (event) => setSettings({
|
|
3139
|
+
...settings,
|
|
3140
|
+
reminderDays: Number(event.target.value)
|
|
3141
|
+
})
|
|
2674
3142
|
})
|
|
3143
|
+
}),
|
|
3144
|
+
/* @__PURE__ */ jsxs("label", {
|
|
3145
|
+
className: "lms-check",
|
|
3146
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
3147
|
+
type: "checkbox",
|
|
3148
|
+
checked: settings.moderateDiscussions,
|
|
3149
|
+
onChange: (event) => setSettings({
|
|
3150
|
+
...settings,
|
|
3151
|
+
moderateDiscussions: event.target.checked
|
|
3152
|
+
})
|
|
3153
|
+
}), "Review discussions before publishing"]
|
|
3154
|
+
}),
|
|
3155
|
+
/* @__PURE__ */ jsxs("label", {
|
|
3156
|
+
className: "lms-check",
|
|
3157
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
3158
|
+
type: "checkbox",
|
|
3159
|
+
checked: settings.moderateReviews,
|
|
3160
|
+
onChange: (event) => setSettings({
|
|
3161
|
+
...settings,
|
|
3162
|
+
moderateReviews: event.target.checked
|
|
3163
|
+
})
|
|
3164
|
+
}), "Review course reviews before publishing"]
|
|
3165
|
+
}),
|
|
3166
|
+
/* @__PURE__ */ jsxs("label", {
|
|
3167
|
+
className: "lms-check",
|
|
3168
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
3169
|
+
type: "checkbox",
|
|
3170
|
+
checked: settings.emailNotifications,
|
|
3171
|
+
onChange: (event) => setSettings({
|
|
3172
|
+
...settings,
|
|
3173
|
+
emailNotifications: event.target.checked
|
|
3174
|
+
})
|
|
3175
|
+
}), "Send notifications through the site's EmDash email transport"]
|
|
3176
|
+
}),
|
|
3177
|
+
/* @__PURE__ */ jsx(Button, {
|
|
3178
|
+
type: "submit",
|
|
3179
|
+
busy: action.busy,
|
|
3180
|
+
children: "Save settings"
|
|
2675
3181
|
})
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
2680
|
-
type: "checkbox",
|
|
2681
|
-
checked: settings.moderateDiscussions,
|
|
2682
|
-
onChange: (event) => setSettings({
|
|
2683
|
-
...settings,
|
|
2684
|
-
moderateDiscussions: event.target.checked
|
|
2685
|
-
})
|
|
2686
|
-
}), "Review discussions before publishing"]
|
|
2687
|
-
}),
|
|
2688
|
-
/* @__PURE__ */ jsxs("label", {
|
|
2689
|
-
className: "lms-check",
|
|
2690
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
2691
|
-
type: "checkbox",
|
|
2692
|
-
checked: settings.moderateReviews,
|
|
2693
|
-
onChange: (event) => setSettings({
|
|
2694
|
-
...settings,
|
|
2695
|
-
moderateReviews: event.target.checked
|
|
2696
|
-
})
|
|
2697
|
-
}), "Review course reviews before publishing"]
|
|
2698
|
-
}),
|
|
2699
|
-
/* @__PURE__ */ jsxs("label", {
|
|
2700
|
-
className: "lms-check",
|
|
2701
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
2702
|
-
type: "checkbox",
|
|
2703
|
-
checked: settings.emailNotifications,
|
|
2704
|
-
onChange: (event) => setSettings({
|
|
2705
|
-
...settings,
|
|
2706
|
-
emailNotifications: event.target.checked
|
|
2707
|
-
})
|
|
2708
|
-
}), "Send notifications through the site's EmDash email transport"]
|
|
2709
|
-
}),
|
|
2710
|
-
/* @__PURE__ */ jsx(Button, {
|
|
2711
|
-
type: "submit",
|
|
2712
|
-
busy: action.busy,
|
|
2713
|
-
children: "Save settings"
|
|
2714
|
-
})
|
|
2715
|
-
]
|
|
2716
|
-
}) })]
|
|
3182
|
+
]
|
|
3183
|
+
}) })
|
|
3184
|
+
]
|
|
2717
3185
|
});
|
|
2718
3186
|
}
|
|
2719
3187
|
function LearningWidget() {
|
|
@@ -2747,6 +3215,7 @@ const pages = {
|
|
|
2747
3215
|
"/assignments": AssignmentsPage,
|
|
2748
3216
|
"/community": CommunityPage,
|
|
2749
3217
|
"/cohorts": CohortsPage,
|
|
3218
|
+
"/paths": PathsPage,
|
|
2750
3219
|
"/reports": ReportsPage,
|
|
2751
3220
|
"/notifications": NotificationsPage,
|
|
2752
3221
|
"/audit": AuditPage,
|
|
@@ -2758,5 +3227,5 @@ const css = `
|
|
|
2758
3227
|
.ag-lms-admin *{box-sizing:border-box}.ag-lms-admin h1{font-size:clamp(1.6rem,3vw,2.15rem);margin:0}.ag-lms-admin h2{font-size:1.15rem;margin-block:0 .8rem}.ag-lms-admin h3{font-size:1rem}.ag-lms-admin p{margin-block:.5rem 1rem}.lms-page-header{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-block-end:1.5rem}.lms-page-header>div>p{max-width:65ch;color:var(--text-color-kumo-subtle,#64748b)}.lms-eyebrow{font-size:.7rem;letter-spacing:.15em;font-weight:700}.lms-tabs{display:flex;gap:.3rem;flex-wrap:wrap;margin-block:1rem 1.5rem}.lms-tabs a{padding:.4rem .7rem;border-radius:.4rem;text-decoration:none;color:inherit;background:var(--color-kumo-control,#f1f5f9)}.lms-panel{background:var(--color-kumo-base,#fff);border:1px solid var(--color-kumo-line,#d1d5db);border-radius:.65rem;padding:1.3rem;margin-block-end:1rem;min-width:0}.lms-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem}.lms-course-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(270px,1fr));gap:1rem}.lms-course-grid .lms-panel{display:flex;flex-direction:column;align-items:flex-start}.lms-course-grid .lms-actions{margin-block-start:auto}.lms-metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1rem}.lms-metrics strong{display:block;font-size:2rem;margin-block-start:.5rem}.lms-metrics span,.lms-muted{font-size:.85rem;color:var(--text-color-kumo-subtle,#64748b)}.lms-actions,.lms-toolbar{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-block:.6rem 1rem}.lms-toolbar>.lms-field{min-width:160px;margin:0}.lms-button{display:inline-flex;justify-content:center;align-items:center;gap:.4rem;min-height:38px;padding:.5rem .8rem;border:1px solid var(--color-kumo-line,#cbd5e1);border-radius:.4rem;background:var(--color-kumo-control,#f8fafc);color:var(--text-color-kumo-default,#1e293b);font:inherit;font-size:.875rem;font-weight:600;text-decoration:none;cursor:pointer}.lms-button:hover{background:var(--color-kumo-interact,#e2e8f0)}.lms-button:disabled{opacity:.55;cursor:default}.lms-danger{color:var(--text-color-kumo-danger,#b91c1c)}.lms-field{display:grid;gap:.35rem;margin-block-end:1rem;min-width:0;font-size:.875rem;font-weight:600}.lms-field small{font-weight:400;color:var(--text-color-kumo-subtle,#64748b)}.ag-lms-admin input:not([type=checkbox]):not([type=radio]),.ag-lms-admin textarea,.ag-lms-admin select{width:100%;border:1px solid var(--color-kumo-line,#cbd5e1);border-radius:.35rem;background:var(--color-kumo-control,#fff);color:inherit;padding:.6rem;font:inherit;font-size:.875rem;font-weight:400}.ag-lms-admin input[type=checkbox],.ag-lms-admin input[type=radio]{width:1rem;height:1rem;accent-color:var(--color-kumo-brand,#2563eb)}.ag-lms-admin :focus-visible{outline:2px solid var(--color-kumo-focus,#2563eb);outline-offset:3px}.lms-check{display:flex;gap:.6rem;align-items:center;font-size:.9rem;margin-block:.7rem}.lms-notice{padding:.8rem 1rem;border:1px solid var(--color-kumo-line,#cbd5e1);border-radius:.4rem;background:var(--color-kumo-tint,#f1f5f9)}.lms-error{color:var(--text-color-kumo-danger,#b91c1c);border-color:currentColor}.lms-badge{font-size:.7rem;text-transform:uppercase;font-weight:700;border:1px solid var(--color-kumo-line,#cbd5e1);border-radius:1rem;padding:.2rem .6rem;margin-block-end:.6rem}.lms-studio{display:grid;grid-template-columns:330px minmax(0,1fr);gap:1.3rem;align-items:start}.lms-module{border-block-end:1px solid var(--color-kumo-line,#cbd5e1);padding-block:1rem}.lms-lesson-list{list-style:none;padding:0;margin:0}.lms-lesson-list li{display:flex;align-items:center;gap:.3rem}.lms-select-lesson{background:transparent;color:inherit;border:0;border-radius:.35rem;cursor:pointer;text-align:start;padding:.6rem;flex:1;font:inherit;font-size:.9rem}.lms-select-lesson[aria-current=true]{background:var(--color-kumo-control,#eff6ff);font-weight:600}.lms-select-lesson small{display:block;font-weight:400;opacity:.75}.lms-lesson-list .lms-actions{margin:0}.lms-option{display:flex;align-items:center;gap:.5rem;margin-block:.5rem}.lms-question{border-block-start:1px solid var(--color-kumo-line,#cbd5e1);padding-block:1rem}.lms-question fieldset{border:1px solid var(--color-kumo-line,#cbd5e1);border-radius:.4rem;margin-block:1rem;padding:1rem}.lms-resource{border-block-end:1px solid var(--color-kumo-line,#cbd5e1);padding-block:1rem}.lms-file{position:absolute;width:1px!important;height:1px;opacity:0}.lms-table-wrap{overflow:auto}.lms-table{width:100%;border-collapse:collapse;font-size:.875rem}.lms-table th,.lms-table td{padding:.7rem;text-align:start;border-block-end:1px solid var(--color-kumo-line,#e5e7eb);vertical-align:top}.lms-table th{font-weight:600}.lms-table small{display:block;color:var(--text-color-kumo-subtle,#64748b)}.lms-table .lms-actions{margin:0}.lms-body{white-space:pre-wrap;overflow-wrap:anywhere}.ag-lms-admin details{padding:.7rem;border:1px solid var(--color-kumo-line,#d1d5db);border-radius:.4rem;margin-block:1rem}.ag-lms-admin summary{cursor:pointer;font-weight:600}.lms-notice:empty{display:none}@media(max-width:900px){.lms-studio{grid-template-columns:1fr}.lms-page-header{align-items:start;flex-direction:column}}@media(max-width:600px){.lms-grid{grid-template-columns:1fr}.lms-toolbar{align-items:stretch}.lms-toolbar>.lms-field{width:100%}.lms-panel{padding:1rem}}`;
|
|
2759
3228
|
|
|
2760
3229
|
//#endregion
|
|
2761
|
-
export { AssignmentsPage, AuditPage, CohortsPage, CommunityPage, CoursesPage, LearnersPage, LearningWidget, NotificationsPage, OverviewPage, ReportsPage, SettingsPage, pages, widgets };
|
|
3230
|
+
export { AssignmentsPage, AuditPage, CohortsPage, CommunityPage, CoursesPage, LearnersPage, LearningWidget, NotificationsPage, OverviewPage, PathsPage, ReportsPage, SettingsPage, pages, widgets };
|
|
2762
3231
|
//# sourceMappingURL=admin.js.map
|