bitsnap-react 1.18.0 → 1.19.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/AGENTS.md +106 -0
- package/README.md +98 -0
- package/dist/BitsnapCart-E72W5RRZ.mjs +2 -0
- package/dist/BitsnapCart-LHJ6KOTV.css +2 -0
- package/dist/BitsnapCart-LHJ6KOTV.css.map +1 -0
- package/dist/BitsnapCart-Q2HER6GU.js +2 -0
- package/dist/BitsnapCart-Q2HER6GU.js.map +1 -0
- package/dist/CartProvider-677CE2FI.js +2 -0
- package/dist/CartProvider-677CE2FI.js.map +1 -0
- package/dist/CartProvider-YPDOFSYC.mjs +2 -0
- package/dist/{PanelModelFiles-JZFSF7BF.mjs → PanelModelFiles-4B2HYGIE.mjs} +2 -2
- package/dist/{PanelModelFiles-7HPXWONN.js → PanelModelFiles-EKQRLF46.js} +2 -2
- package/dist/PanelModelFiles-EKQRLF46.js.map +1 -0
- package/dist/analytics.js +1 -1
- package/dist/analytics.js.map +1 -1
- package/dist/analytics.mjs +1 -1
- package/dist/catalog.js +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/catalog.mjs +1 -1
- package/dist/chat.css +2 -0
- package/dist/chat.css.map +1 -0
- package/dist/chat.d.mts +172 -0
- package/dist/chat.d.ts +172 -0
- package/dist/chat.js +2 -0
- package/dist/chat.js.map +1 -0
- package/dist/chat.mjs +2 -0
- package/dist/chat.mjs.map +1 -0
- package/dist/chunk-2FD2QP4Y.js +2 -0
- package/dist/chunk-2FD2QP4Y.js.map +1 -0
- package/dist/chunk-5JRUPLXY.mjs +2 -0
- package/dist/chunk-5JRUPLXY.mjs.map +1 -0
- package/dist/chunk-5M6M7FAR.mjs +2 -0
- package/dist/chunk-5M6M7FAR.mjs.map +1 -0
- package/dist/chunk-6Q3VGPSF.js.map +1 -1
- package/dist/chunk-AAC576BC.mjs +5 -0
- package/dist/chunk-AAC576BC.mjs.map +1 -0
- package/dist/{chunk-4OVOKGGS.mjs → chunk-AB37DWOK.mjs} +2 -2
- package/dist/{chunk-ZATPBYOA.js → chunk-BUYTK7KM.js} +2 -2
- package/dist/chunk-BUYTK7KM.js.map +1 -0
- package/dist/{chunk-DP2NOHCI.js → chunk-D2ASWUXM.js} +2 -2
- package/dist/chunk-D2ASWUXM.js.map +1 -0
- package/dist/chunk-DDIEFKNP.mjs +2 -0
- package/dist/{chunk-ZHTMK5TA.mjs.map → chunk-DDIEFKNP.mjs.map} +1 -1
- package/dist/{chunk-4HGZZJEL.js → chunk-DOJICJCQ.js} +2 -2
- package/dist/chunk-DOJICJCQ.js.map +1 -0
- package/dist/chunk-EMR4YXNY.mjs +2 -0
- package/dist/chunk-EMR4YXNY.mjs.map +1 -0
- package/dist/{chunk-JA62KNAQ.js → chunk-GJOGATP6.js} +2 -2
- package/dist/chunk-GJOGATP6.js.map +1 -0
- package/dist/chunk-GQ5OKU74.js +2 -0
- package/dist/chunk-GQ5OKU74.js.map +1 -0
- package/dist/chunk-JSDGZ4NY.js.map +1 -1
- package/dist/chunk-KHQEDQYF.js +5 -0
- package/dist/chunk-KHQEDQYF.js.map +1 -0
- package/dist/chunk-KOYBZORL.js.map +1 -1
- package/dist/chunk-LGPNJ5XE.js +2 -0
- package/dist/chunk-LGPNJ5XE.js.map +1 -0
- package/dist/chunk-MDAFPGPA.js +2 -0
- package/dist/chunk-MDAFPGPA.js.map +1 -0
- package/dist/{chunk-JHJ3ZZF4.mjs → chunk-MMLBQHBM.mjs} +2 -2
- package/dist/chunk-MWWEPEU2.js +2 -0
- package/dist/chunk-MWWEPEU2.js.map +1 -0
- package/dist/{chunk-GEBRXFT3.mjs → chunk-N523LFAM.mjs} +2 -2
- package/dist/chunk-NWXZ45TV.js +2 -0
- package/dist/chunk-NWXZ45TV.js.map +1 -0
- package/dist/chunk-OFPRIENJ.mjs +2 -0
- package/dist/chunk-OFPRIENJ.mjs.map +1 -0
- package/dist/chunk-OR566YJI.mjs +2 -0
- package/dist/chunk-OR566YJI.mjs.map +1 -0
- package/dist/chunk-Q337IQNA.js +2 -0
- package/dist/chunk-Q337IQNA.js.map +1 -0
- package/dist/chunk-SCEWYZUZ.mjs +2 -0
- package/dist/chunk-SCEWYZUZ.mjs.map +1 -0
- package/dist/{chunk-CF45RDMD.mjs → chunk-SSSF64M7.mjs} +2 -2
- package/dist/{chunk-CF45RDMD.mjs.map → chunk-SSSF64M7.mjs.map} +1 -1
- package/dist/chunk-W3RGQYPD.js.map +1 -1
- package/dist/chunk-WG54EHST.mjs +2 -0
- package/dist/chunk-WG54EHST.mjs.map +1 -0
- package/dist/chunk-XEXPQFKN.mjs +2 -0
- package/dist/chunk-XEXPQFKN.mjs.map +1 -0
- package/dist/chunk-YV2C276H.js +2 -0
- package/dist/chunk-YV2C276H.js.map +1 -0
- package/dist/consent.js.map +1 -1
- package/dist/course.d.mts +2 -14
- package/dist/course.d.ts +2 -14
- package/dist/course.js +1 -1
- package/dist/course.js.map +1 -1
- package/dist/course.mjs +1 -1
- package/dist/course.mjs.map +1 -1
- package/dist/embed/analytics.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +288 -4
- package/dist/index.d.ts +288 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/methods-64D5DD6K.js +2 -0
- package/dist/methods-64D5DD6K.js.map +1 -0
- package/dist/methods-NQA3DFLE.mjs +2 -0
- package/dist/methods-NQA3DFLE.mjs.map +1 -0
- package/dist/model-files-client-P4PORZJ6.js.map +1 -1
- package/dist/models.js.map +1 -1
- package/dist/returns.js +1 -1
- package/dist/returns.js.map +1 -1
- package/dist/returns.mjs +1 -1
- package/dist/texts-9kfgClsA.d.mts +14 -0
- package/dist/texts-9kfgClsA.d.ts +14 -0
- package/dist/{types-D6LsoycW.d.mts → types-B-sjyZQs.d.mts} +7 -1
- package/dist/{types-ClFCVzk_.d.ts → types-iRe6ix2y.d.ts} +7 -1
- package/dist/viewer3d.js.map +1 -1
- package/dist/viewer3d.mjs +1 -1
- package/dist/viewer3d.mjs.map +1 -1
- package/dist/widgets-BT2LN2Nq.d.mts +547 -0
- package/dist/widgets-BVOSj9fU.d.ts +547 -0
- package/package.json +18 -3
- package/dist/CartProvider-OEI526IN.js +0 -2
- package/dist/CartProvider-OEI526IN.js.map +0 -1
- package/dist/CartProvider-RYAA3JDA.mjs +0 -2
- package/dist/PanelModelFiles-7HPXWONN.js.map +0 -1
- package/dist/chunk-4HGZZJEL.js.map +0 -1
- package/dist/chunk-5X7TP3TQ.mjs +0 -2
- package/dist/chunk-5X7TP3TQ.mjs.map +0 -1
- package/dist/chunk-7RXW6XFY.js +0 -2
- package/dist/chunk-7RXW6XFY.js.map +0 -1
- package/dist/chunk-DP2NOHCI.js.map +0 -1
- package/dist/chunk-EVIDNS22.mjs +0 -2
- package/dist/chunk-EVIDNS22.mjs.map +0 -1
- package/dist/chunk-G4V2KWTV.js +0 -2
- package/dist/chunk-G4V2KWTV.js.map +0 -1
- package/dist/chunk-GWF3MRUP.js +0 -2
- package/dist/chunk-GWF3MRUP.js.map +0 -1
- package/dist/chunk-JA62KNAQ.js.map +0 -1
- package/dist/chunk-UPAOIY4R.mjs +0 -2
- package/dist/chunk-UPAOIY4R.mjs.map +0 -1
- package/dist/chunk-V5ECGBFW.js +0 -5
- package/dist/chunk-V5ECGBFW.js.map +0 -1
- package/dist/chunk-VNV5BD4X.js +0 -2
- package/dist/chunk-VNV5BD4X.js.map +0 -1
- package/dist/chunk-VNYHJT6V.mjs +0 -2
- package/dist/chunk-VNYHJT6V.mjs.map +0 -1
- package/dist/chunk-WJO5GSY3.mjs +0 -5
- package/dist/chunk-WJO5GSY3.mjs.map +0 -1
- package/dist/chunk-ZATPBYOA.js.map +0 -1
- package/dist/chunk-ZHTMK5TA.mjs +0 -2
- package/dist/methods-AULQDKVS.mjs +0 -2
- package/dist/methods-PMH5VQ25.js +0 -2
- package/dist/methods-PMH5VQ25.js.map +0 -1
- /package/dist/{CartProvider-RYAA3JDA.mjs.map → BitsnapCart-E72W5RRZ.mjs.map} +0 -0
- /package/dist/{methods-AULQDKVS.mjs.map → CartProvider-YPDOFSYC.mjs.map} +0 -0
- /package/dist/{PanelModelFiles-JZFSF7BF.mjs.map → PanelModelFiles-4B2HYGIE.mjs.map} +0 -0
- /package/dist/{chunk-4OVOKGGS.mjs.map → chunk-AB37DWOK.mjs.map} +0 -0
- /package/dist/{chunk-JHJ3ZZF4.mjs.map → chunk-MMLBQHBM.mjs.map} +0 -0
- /package/dist/{chunk-GEBRXFT3.mjs.map → chunk-N523LFAM.mjs.map} +0 -0
package/dist/course.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/course.js","../src/course/PanelCourse.tsx","../src/course/accent.ts","../src/course/analytics.ts","../src/course/context.tsx","../src/course/CourseOutline.tsx","../src/course/logic.ts"],"names":["accentVars","colors","vars","brand","brandDark","foreground","readableForeground","CourseViewContext","createContext","CourseViewProvider","useCourseView","value","useContext","useCourseTrack","analytics","preview","useCallback","event","props","flattenItems","course","section","item","findItem","itemId","getAdjacentItems","items","index","getAutoplayTarget","next","computeProgress","total","completed","markLessonCompleted","lessonId","sections","progress","resolveInitialItemId","requestedId","fillTemplate","template","values","match","key","isPlaybackComplete","positionSeconds","durationSeconds","resumePosition","formatDuration","totalSeconds","seconds","h","m","s"],"mappings":"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","file":"/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/course.js","sourcesContent":[null,"import { ChevronLeft, List, Play } from \"lucide-react\";\nimport {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n InstrumentProvider,\n useInstrument,\n type AnalyticsOption,\n} from \"../analytics/instrument\";\nimport { usePanelConfig } from \"../panel/PanelProvider\";\nimport type { GetThemeOutput } from \"../panel/types\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../ui/button\";\nimport { accentVars } from \"./accent\";\nimport { useCourseTrack } from \"./analytics\";\nimport { CourseViewProvider, useCourseView } from \"./context\";\nimport { CourseOutline } from \"./CourseOutline\";\nimport { createRpcCourseDataSource } from \"./rpc\";\nimport { LessonPane } from \"./LessonPane\";\nimport {\n fillTemplate,\n findItem,\n flattenItems,\n getAdjacentItems,\n getAutoplayTarget,\n markLessonCompleted,\n resolveInitialItemId,\n type FlatItem,\n} from \"./logic\";\nimport { getCourseTexts, type CourseTexts } from \"./texts\";\nimport { TestPane } from \"./TestPane\";\nimport {\n CourseError,\n type Course,\n type CourseDataSource,\n type ItemRef,\n type Lesson,\n type ProgressSaved,\n} from \"./types\";\nimport { CARD, ErrorBox, LoadingBlock, ProgressBar, errorMessage } from \"./ui\";\nimport { useAsyncResource } from \"./useAsyncResource\";\n\nexport type PanelCourseProps = {\n /** `UserProduct.access_id` of the purchased course. Not needed with `dataSource`. */\n accessId?: string;\n /** Panel session token; read from the panel session when omitted. */\n accessToken?: string;\n /**\n * Replaces the default data source (`UserPanelCourseService` through the\n * panel's transport). The creator preview of the dashboard passes its own.\n */\n dataSource?: CourseDataSource;\n /** Creator preview: shows a badge and never saves progress. */\n preview?: boolean;\n /** The open lesson or test. Controlled together with `onItemChange`. */\n itemId?: string;\n onItemChange?: (itemId: string) => void;\n /** Shows a back button. */\n onBack?: () => void;\n /** Course structure to render right away (server rendering, preview). */\n initialCourse?: Course;\n /** Text overrides; Polish and English defaults by `locale`. */\n texts?: Partial<CourseTexts>;\n /** Defaults to the locale of the library (`setLocale`). */\n locale?: string;\n /** Brand colors; the panel theme (`setTheme`) by default. */\n theme?: GetThemeOutput;\n className?: string;\n /**\n * Usage statistics for your Bitsnap dashboard (course opened, lessons\n * started and completed, video progress, tests, ratings). On by default;\n * `false` turns it off, and the creator `preview` never reports. Inside\n * `<Panel>` the panel's setting applies unless this is given. An object\n * lets you forward every event: `{ onEvent }`. Nothing is stored on the\n * visitor's device.\n */\n analytics?: AnalyticsOption;\n};\n\nconst AUTOPLAY_KEY = \"bitsnap-course-autoplay\";\n\nfunction useAutoplayPreference(): [boolean, (value: boolean) => void] {\n const [value, setValue] = useState(false);\n useEffect(() => {\n try {\n setValue(window.localStorage.getItem(AUTOPLAY_KEY) === \"1\");\n } catch {\n // Storage is not available (private mode); keep the default.\n }\n }, []);\n const update = useCallback((next: boolean) => {\n setValue(next);\n try {\n window.localStorage.setItem(AUTOPLAY_KEY, next ? \"1\" : \"0\");\n } catch {\n // Not persisted.\n }\n }, []);\n return [value, update];\n}\n\nfunction toRef(item: FlatItem | undefined): ItemRef | undefined {\n return item == null ? undefined : { id: item.id, kind: item.kind };\n}\n\nfunction LockedPane({ item }: { item: FlatItem }) {\n const { texts } = useCourseView();\n return (\n <div className={cn(CARD, \"space-y-2\")} role=\"status\">\n <h2 className=\"text-2xl font-semibold break-words\">{item.title}</h2>\n <p className=\"text-sm text-neutral-600 dark:text-neutral-300\">\n {item.lockReason\n ? fillTemplate(texts.lockedPage, {\n test: item.lockReason.gatingTestTitle,\n })\n : texts.locked}\n </p>\n </div>\n );\n}\n\ntype LessonContainerProps = {\n course: Course;\n item: FlatItem;\n autoplay: boolean;\n onAutoplayChange: (value: boolean) => void;\n startPlaying: boolean;\n onNavigate: (ref: ItemRef) => void;\n onProgressSaved: (lessonId: string, saved: ProgressSaved) => void;\n onEnded: () => void;\n onLocked: () => void;\n mobileOutline: ReactNode;\n};\n\nfunction LessonContainer({\n course,\n item,\n autoplay,\n onAutoplayChange,\n startPlaying,\n onNavigate,\n onProgressSaved,\n onEnded,\n onLocked,\n mobileOutline,\n}: LessonContainerProps) {\n const { dataSource, texts } = useCourseView();\n const lesson = useAsyncResource<Lesson>(\n () => dataSource.getLesson(item.id),\n [dataSource, item.id],\n );\n\n const lockedByServer =\n lesson.status === \"error\" &&\n lesson.error instanceof CourseError &&\n lesson.error.code === \"item_locked\";\n useEffect(() => {\n // The structure on screen is stale: take the locks from the server again.\n if (lockedByServer) onLocked();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [lockedByServer]);\n\n if (lesson.status === \"loading\") {\n return <LoadingBlock />;\n }\n if (lesson.status === \"error\") {\n return lockedByServer ? (\n <div className={cn(CARD, \"space-y-2\")} role=\"alert\">\n <h2 className=\"text-2xl font-semibold break-words\">{item.title}</h2>\n <p className=\"text-sm\">{errorMessage(lesson.error, texts)}</p>\n </div>\n ) : (\n <ErrorBox error={lesson.error} onRetry={lesson.reload} />\n );\n }\n\n const adjacent = getAdjacentItems(course, item.id);\n const position =\n flattenItems(course)\n .filter((entry) => entry.kind !== \"test\")\n .findIndex((entry) => entry.id === item.id) + 1;\n return (\n <LessonPane\n key={lesson.data.id}\n lesson={lesson.data}\n position={position}\n mobileOutline={mobileOutline}\n nextItem={findItem(\n course,\n (lesson.data.next ?? toRef(adjacent.next))?.id,\n )}\n courseType={course.type}\n prev={lesson.data.prev ?? toRef(adjacent.prev)}\n next={lesson.data.next ?? toRef(adjacent.next)}\n autoplay={autoplay}\n onAutoplayChange={onAutoplayChange}\n startPlaying={startPlaying}\n onNavigate={onNavigate}\n onProgressSaved={onProgressSaved}\n onEnded={onEnded}\n onUrlsExpired={lesson.refresh}\n />\n );\n}\n\nfunction CourseView({\n course,\n selectedId,\n onSelect,\n onBack,\n onCourseChange,\n onCourseRefresh,\n}: {\n course: Course;\n selectedId?: string;\n onSelect: (itemId: string) => void;\n onBack?: () => void;\n onCourseChange: (update: (course: Course) => Course) => void;\n onCourseRefresh: () => void;\n}) {\n const { texts, preview } = useCourseView();\n const track = useCourseTrack();\n const rootRef = useRef<HTMLDivElement>(null);\n const contentsId = useId();\n const [contentsOpen, setContentsOpen] = useState(false);\n const [autoplay, setAutoplay] = useAutoplayPreference();\n const [playOnOpen, setPlayOnOpen] = useState<string>();\n\n const currentId = resolveInitialItemId(course, selectedId);\n const item = findItem(course, currentId);\n\n // Once per opening of the course.\n useEffect(() => {\n track(\"course_opened\", {});\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [course.id]);\n\n const open = (itemId: string, play = false) => {\n setPlayOnOpen(play ? itemId : undefined);\n setContentsOpen(false);\n onSelect(itemId);\n rootRef.current?.scrollIntoView({ block: \"start\", behavior: \"smooth\" });\n };\n\n const outline = (\n <CourseOutline\n course={course}\n selectedId={currentId}\n variant={course.type === \"video\" ? \"playlist\" : \"modules\"}\n onSelect={(entry) => open(entry.id)}\n />\n );\n\n let content;\n if (item == null) {\n content = <p className=\"text-sm text-neutral-500\">{texts.noItem}</p>;\n } else if (item.locked) {\n content = <LockedPane item={item} />;\n } else if (item.kind === \"test\") {\n content = (\n <TestPane\n key={item.id}\n testId={item.id}\n title={item.title}\n onSubmitted={() => onCourseRefresh()}\n />\n );\n } else {\n content = (\n <LessonContainer\n key={item.id}\n course={course}\n item={item}\n autoplay={autoplay}\n onAutoplayChange={setAutoplay}\n startPlaying={playOnOpen === item.id}\n onNavigate={(ref) => open(ref.id)}\n onProgressSaved={(lessonId, saved) =>\n onCourseChange((current) => ({\n ...(saved.completed\n ? markLessonCompleted(current, lessonId)\n : current),\n completedLessons: saved.completedLessons,\n progressPercent: saved.progressPercent,\n }))\n }\n onEnded={() => {\n if (!autoplay) return;\n const target = getAutoplayTarget(course, item.id);\n if (target) open(target.id, true);\n }}\n onLocked={onCourseRefresh}\n mobileOutline={outline}\n />\n );\n }\n\n const isVideo = course.type === \"video\";\n const showMobileContents =\n !isVideo || item == null || item.locked || item.kind === \"test\";\n const showResume =\n course.resumeItemId !== \"\" &&\n course.resumeItemId !== currentId &&\n findItem(course, course.resumeItemId)?.locked === false;\n\n return (\n <div ref={rootRef} className=\"space-y-4 scroll-mt-4\">\n <header className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n {preview && (\n <span\n data-course-preview\n className=\"rounded-full bg-amber-100 px-3 py-0.5 text-xs font-semibold text-amber-900 dark:bg-amber-900/40 dark:text-amber-200\"\n >\n {texts.previewBadge}\n </span>\n )}\n </div>\n <div className=\"flex flex-wrap items-end justify-between gap-4\">\n <div className=\"min-w-0 space-y-1\">\n <h1 className=\"text-2xl font-semibold break-words sm:text-3xl\">\n {course.title}\n </h1>\n </div>\n {showResume && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => open(course.resumeItemId)}\n >\n <Play aria-hidden />\n {texts.continueCourse}\n </Button>\n )}\n </div>\n <div className=\"max-w-xl space-y-1 md:max-w-none\">\n {course.progressPercent >= 100 && (\n <p\n role=\"status\"\n className=\"font-semibold text-emerald-700 dark:text-emerald-400\"\n >\n {texts.courseCompleted ?? texts.completed}\n </p>\n )}\n <ProgressBar\n percent={course.progressPercent}\n label={fillTemplate(texts.progressPercent, {\n percent: course.progressPercent,\n })}\n />\n <p className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {fillTemplate(texts.progressLessons, {\n done: course.completedLessons,\n total: course.totalLessons,\n })}\n {\" · \"}\n {fillTemplate(texts.progressPercent, {\n percent: course.progressPercent,\n })}\n </p>\n </div>\n </header>\n\n <div\n className={cn(\n \"grid gap-6\",\n isVideo\n ? \"lg:grid-cols-[minmax(0,1fr)_380px]\"\n : \"lg:grid-cols-[320px_minmax(0,1fr)]\",\n )}\n >\n {showMobileContents && (\n <div className=\"lg:hidden\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"min-h-11\"\n aria-controls={contentsId}\n aria-expanded={contentsOpen}\n onClick={() => setContentsOpen((value) => !value)}\n >\n <List aria-hidden />\n {texts.contents}\n </Button>\n </div>\n )}\n\n {showMobileContents && (\n <aside\n id={contentsId}\n className={cn(\n CARD,\n isVideo && \"lg:hidden\",\n \"p-3 sm:p-3 lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto\",\n contentsOpen ? \"block\" : \"hidden\",\n !isVideo && \"lg:block\",\n )}\n >\n {outline}\n </aside>\n )}\n\n <div className=\"min-w-0\" data-course-content>\n {content}\n </div>\n\n {isVideo && (\n <aside\n className={cn(\n CARD,\n \"hidden p-3 sm:p-3 lg:block lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto\",\n )}\n >\n {outline}\n </aside>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The course of a customer inside the panel: structure with progress and\n * locks, lessons with video, text and attachments, tests, questions and\n * ratings. Standard courses get a sidebar of modules, video courses a\n * playlist next to the player.\n *\n * Load it lazily from `bitsnap-react/course`. Inside the panel it needs only\n * `accessId` of the purchased course.\n */\nexport function PanelCourse({\n accessId,\n accessToken,\n dataSource: customDataSource,\n preview = false,\n itemId,\n onItemChange,\n onBack,\n initialCourse,\n texts: textOverrides,\n locale,\n theme,\n className,\n analytics,\n}: PanelCourseProps) {\n const panel = usePanelConfig();\n // The creator preview reports nothing, not even the page view of the page it sits on.\n const instrument = useInstrument(panel.projectID, preview ? false : analytics, \"course\");\n const texts = useMemo(\n () => getCourseTexts(textOverrides, locale),\n [textOverrides, locale],\n );\n const dataSource = useMemo(() => {\n if (customDataSource) return customDataSource;\n if (accessId == null || accessId === \"\") {\n throw new Error(\"PanelCourse needs an accessId or a dataSource\");\n }\n return createRpcCourseDataSource({ accessId, accessToken });\n }, [customDataSource, accessId, accessToken]);\n\n const course = useAsyncResource<Course>(\n () => dataSource.getCourse(),\n [dataSource],\n initialCourse,\n );\n\n const [internalItemId, setInternalItemId] = useState<string>();\n // With `onItemChange` the owner navigates, so no `itemId` means the default\n // item, not the one picked last.\n const selectedId = onItemChange ? itemId : (itemId ?? internalItemId);\n const select = useCallback(\n (next: string) => {\n setInternalItemId(next);\n onItemChange?.(next);\n },\n [onItemChange],\n );\n\n const style = accentVars(theme?.colors ?? panel.theme?.colors);\n\n const courseId = course.status === \"success\" ? course.data.id : undefined;\n const viewValue = useMemo(\n () => ({\n dataSource,\n texts,\n preview,\n analytics: { courseId, instrument },\n }),\n [dataSource, texts, preview, courseId, instrument],\n );\n\n return (\n <InstrumentProvider value={instrument}>\n <CourseViewProvider value={viewValue}>\n <div\n data-bitsnap-course\n data-course-type={course.status === \"success\" ? course.data.type : undefined}\n style={style}\n className={cn(\"font-inter w-full max-w-7xl\", className)}\n >\n {course.status === \"loading\" && (\n <div className=\"space-y-4\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n <p className=\"sr-only\" role=\"status\">\n {texts.loading}\n </p>\n <LoadingBlock />\n </div>\n )}\n {course.status === \"error\" && (\n <div className=\"space-y-4\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n <ErrorBox error={course.error} onRetry={course.reload} />\n </div>\n )}\n {course.status === \"success\" && (\n <CourseView\n course={course.data}\n selectedId={selectedId}\n onSelect={select}\n onBack={onBack}\n onCourseChange={course.mutate}\n onCourseRefresh={course.refresh}\n />\n )}\n </div>\n </CourseViewProvider>\n </InstrumentProvider>\n );\n}\n\nexport default PanelCourse;\n","import type { CSSProperties } from \"react\";\nimport { readableForeground } from \"../components/returns/color\";\n\n/**\n * CSS variables of the accent (brand) color of the course view. Without a\n * brand color nothing is set and the library's `--primary` applies.\n */\nexport function accentVars(colors?: {\n brand?: string;\n brandDark?: string;\n}): CSSProperties {\n const vars: Record<string, string> = {};\n const brand = colors?.brand?.trim();\n const brandDark = colors?.brandDark?.trim() || brand;\n if (brand) {\n vars[\"--course-accent\"] = brand;\n const foreground = readableForeground(brand);\n if (foreground) vars[\"--course-accent-fg\"] = foreground;\n }\n if (brandDark) {\n vars[\"--course-accent-dark\"] = brandDark;\n const foreground = readableForeground(brandDark);\n if (foreground) vars[\"--course-accent-dark-fg\"] = foreground;\n }\n return vars as CSSProperties;\n}\n","import { useCallback } from \"react\";\nimport type { EventProps } from \"@/src/analytics/contract\";\nimport { useCourseView } from \"./context\";\n\ntype CourseEventName =\n | \"course_opened\"\n | \"lesson_started\"\n | \"lesson_completed\"\n | \"video_progress\"\n | \"test_submitted\"\n | \"lesson_rated\";\n\n/**\n * Reports a course event; `course_id` is added. Nothing is reported in the\n * creator preview, before the course has loaded, or without analytics.\n */\nexport function useCourseTrack() {\n const { analytics, preview } = useCourseView();\n return useCallback(\n <E extends CourseEventName>(\n event: E,\n props: Omit<EventProps<E>, \"course_id\">,\n ) => {\n if (preview || analytics?.courseId == null) {\n return;\n }\n analytics.instrument.track(event, {\n ...props,\n course_id: analytics.courseId,\n } as unknown as EventProps<E>);\n },\n [analytics, preview],\n );\n}\n","import { createContext, useContext } from \"react\";\nimport type { Instrument } from \"@/src/analytics/instrument\";\nimport type { CourseTexts } from \"./texts\";\nimport type { CourseDataSource } from \"./types\";\n\nexport type CourseAnalytics = {\n /** `undefined` until the course has loaded. */\n courseId?: string;\n instrument: Instrument;\n};\n\nexport type CourseViewContextValue = {\n dataSource: CourseDataSource;\n texts: CourseTexts;\n /** Creator preview: nothing the viewer does is saved as progress. */\n preview: boolean;\n /** Usage statistics; none without it. */\n analytics?: CourseAnalytics;\n};\n\nconst CourseViewContext = createContext<CourseViewContextValue | undefined>(\n undefined,\n);\n\nexport const CourseViewProvider = CourseViewContext.Provider;\n\nexport function useCourseView(): CourseViewContextValue {\n const value = useContext(CourseViewContext);\n if (value == null) {\n throw new Error(\"Course components have to be rendered inside PanelCourse\");\n }\n return value;\n}\n","import {\n CheckCircle2,\n ClipboardCheck,\n Lock,\n PlayCircle,\n FileText,\n} from \"lucide-react\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useCourseView } from \"./context\";\nimport { fillTemplate, formatDuration } from \"./logic\";\nimport type { Course, CourseItem } from \"./types\";\n\ntype CourseOutlineProps = {\n course: Course;\n selectedId?: string;\n onSelect: (item: CourseItem) => void;\n /**\n * `playlist` (video courses) shows the thumbnail and duration of every\n * video; `modules` (standard courses) is a plain list of lessons.\n */\n variant: \"modules\" | \"playlist\";\n className?: string;\n};\n\nfunction ItemIcon({ item }: { item: CourseItem }) {\n if (item.locked) {\n return <Lock className=\"size-4 text-neutral-500\" aria-hidden />;\n }\n if (item.completed) {\n return (\n <CheckCircle2\n className=\"size-4 text-emerald-600 dark:text-emerald-400\"\n aria-hidden\n />\n );\n }\n if (item.kind === \"test\") {\n return <ClipboardCheck className=\"size-4 text-neutral-500\" aria-hidden />;\n }\n return item.durationSeconds > 0 ? (\n <PlayCircle className=\"size-4 text-neutral-500\" aria-hidden />\n ) : (\n <FileText className=\"size-4 text-neutral-500\" aria-hidden />\n );\n}\n\nfunction OutlineItem({\n item,\n selected,\n variant,\n onSelect,\n}: {\n item: CourseItem;\n selected: boolean;\n variant: \"modules\" | \"playlist\";\n onSelect: (item: CourseItem) => void;\n}) {\n const { texts } = useCourseView();\n const lockText = item.lockReason\n ? fillTemplate(texts.lockedByTest, {\n test: item.lockReason.gatingTestTitle,\n })\n : texts.locked;\n const state = item.locked\n ? lockText\n : item.completed\n ? texts.completed\n : undefined;\n\n return (\n <li>\n <button\n type=\"button\"\n data-item-id={item.id}\n data-locked={item.locked || undefined}\n aria-current={selected ? \"true\" : undefined}\n aria-disabled={item.locked || undefined}\n title={item.locked ? lockText : undefined}\n onClick={() => {\n if (!item.locked) onSelect(item);\n }}\n className={cn(\n \"flex min-h-11 w-full items-start gap-3 rounded-lg p-2 text-left text-sm outline-none transition focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n selected\n ? \"bg-neutral-200 dark:bg-neutral-700\"\n : \"hover:bg-neutral-100 dark:hover:bg-neutral-800\",\n item.locked &&\n \"cursor-not-allowed opacity-60 hover:bg-transparent dark:hover:bg-transparent\",\n )}\n >\n {variant === \"playlist\" && item.kind === \"lesson\" && (\n <span className=\"relative aspect-video w-24 shrink-0 overflow-hidden rounded bg-neutral-200 dark:bg-neutral-800\">\n {item.thumbnailUrl && (\n <img\n src={item.thumbnailUrl}\n alt=\"\"\n loading=\"lazy\"\n draggable={false}\n className=\"size-full object-cover\"\n />\n )}\n {item.durationSeconds > 0 && (\n <span className=\"absolute right-1 bottom-1 rounded bg-black/75 px-1 text-[10px] text-white\">\n {formatDuration(item.durationSeconds)}\n </span>\n )}\n </span>\n )}\n {(variant === \"modules\" || item.kind === \"test\") && (\n <span className=\"mt-0.5 shrink-0\">\n <ItemIcon item={item} />\n </span>\n )}\n <span className=\"min-w-0 flex-1\">\n <span className=\"block font-medium break-words\">{item.title}</span>\n <span className=\"mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-neutral-500 dark:text-neutral-400\">\n {item.kind === \"test\" && <span>{texts.testBadge}</span>}\n {item.isFree && !item.locked && <span>{texts.free}</span>}\n {variant === \"modules\" && item.durationSeconds > 0 && (\n <span>{formatDuration(item.durationSeconds)}</span>\n )}\n {state && <span>{state}</span>}\n </span>\n </span>\n {variant === \"playlist\" && item.kind === \"lesson\" && (\n <span className=\"mt-0.5 shrink-0\">\n <ItemIcon item={item} />\n </span>\n )}\n </button>\n </li>\n );\n}\n\n/** Modules / sections with their lessons and tests. */\nexport function CourseOutline({\n course,\n selectedId,\n onSelect,\n variant,\n className,\n}: CourseOutlineProps) {\n const { texts } = useCourseView();\n const outlineId = useId();\n const rootRef = useRef<HTMLElement>(null);\n const currentSection = course.sections.find((section) =>\n section.items.some((item) => item.id === selectedId),\n )?.id;\n const [openSections, setOpenSections] = useState<Set<string>>(\n () => new Set(currentSection ? [currentSection] : []),\n );\n useEffect(() => {\n if (currentSection)\n setOpenSections((current) => new Set([...current, currentSection]));\n }, [currentSection]);\n useEffect(() => {\n const selected =\n rootRef.current?.querySelector<HTMLElement>(\"[aria-current]\");\n if (selected && !selected.closest(\"[hidden]\"))\n selected.scrollIntoView?.({ block: \"nearest\" });\n }, [selectedId, openSections]);\n\n const renderItems = (items: CourseItem[]) => (\n <ul className=\"flex flex-col gap-1\">\n {items.map((item) => (\n <OutlineItem\n key={item.id}\n item={item}\n selected={item.id === selectedId}\n variant={variant}\n onSelect={onSelect}\n />\n ))}\n </ul>\n );\n\n return (\n <nav\n ref={rootRef}\n aria-label={texts.contents}\n className={cn(\"space-y-4\", className)}\n >\n {course.sections.map((section) => {\n const lessons = section.items.filter((i) => i.kind === \"lesson\");\n const done = lessons.filter((i) => i.completed).length;\n return (\n <section key={section.id} data-section-id={section.id}>\n <h3 className=\"mb-1 text-sm font-semibold\">\n <button\n type=\"button\"\n className=\"flex min-h-11 w-full items-center justify-between gap-2 rounded-lg px-2 text-left focus-visible:outline focus-visible:outline-2\"\n aria-expanded={openSections.has(section.id)}\n aria-controls={`${outlineId}-${section.id}`}\n onClick={() =>\n setOpenSections((current) => {\n const next = new Set(current);\n if (next.has(section.id)) next.delete(section.id);\n else next.add(section.id);\n return next;\n })\n }\n >\n <span className=\"break-words\">{section.title}</span>\n {lessons.length > 0 && (\n <span className=\"shrink-0 text-xs font-normal text-neutral-500 dark:text-neutral-400\">\n {done}/{lessons.length}\n </span>\n )}\n </button>\n </h3>\n <div\n id={`${outlineId}-${section.id}`}\n hidden={!openSections.has(section.id)}\n >\n {renderItems(section.items)}\n </div>\n </section>\n );\n })}\n {course.finalItems.length > 0 && (\n <section data-section-id=\"final\">\n {renderItems(course.finalItems)}\n </section>\n )}\n </nav>\n );\n}\n","import type {\n Course,\n CourseItem,\n QuestionAnswer,\n TestAnswers,\n TestQuestion,\n TestQuestionConfig,\n} from \"./types\";\n\n/** Playback share from which a lesson counts as watched. */\nexport const COMPLETE_AT_FRACTION = 0.8;\n\n/** How often the player reports the position while it plays. */\nexport const PROGRESS_SAVE_INTERVAL_MS = 15_000;\n\n/** An item together with the section it sits in (empty for final tests). */\nexport type FlatItem = CourseItem & { sectionId: string };\n\n/** All items of the course in the order they are taken. */\nexport function flattenItems(course: Course): FlatItem[] {\n return [\n ...course.sections.flatMap((section) =>\n section.items.map((item) => ({ ...item, sectionId: section.id })),\n ),\n ...course.finalItems.map((item) => ({ ...item, sectionId: \"\" })),\n ];\n}\n\nexport function findItem(\n course: Course,\n itemId: string | undefined,\n): FlatItem | undefined {\n if (itemId == null || itemId === \"\") {\n return undefined;\n }\n return flattenItems(course).find((item) => item.id === itemId);\n}\n\nexport function getAdjacentItems(\n course: Course,\n itemId: string,\n): { prev?: FlatItem; next?: FlatItem } {\n const items = flattenItems(course);\n const index = items.findIndex((item) => item.id === itemId);\n if (index < 0) {\n return {};\n }\n return { prev: items[index - 1], next: items[index + 1] };\n}\n\n/**\n * The item to start autoplay with after `itemId` ended: the next item when it\n * is an unlocked lesson. A test or a locked lesson stops the playlist.\n */\nexport function getAutoplayTarget(\n course: Course,\n itemId: string,\n): FlatItem | undefined {\n const { next } = getAdjacentItems(course, itemId);\n return next != null && next.kind === \"lesson\" && !next.locked\n ? next\n : undefined;\n}\n\n/** Progress counted from the lessons of the structure (tests do not count). */\nexport function computeProgress(course: Pick<Course, \"sections\">): {\n total: number;\n completed: number;\n percent: number;\n} {\n let total = 0;\n let completed = 0;\n for (const section of course.sections) {\n for (const item of section.items) {\n if (item.kind !== \"lesson\") {\n continue;\n }\n total += 1;\n if (item.completed) {\n completed += 1;\n }\n }\n }\n return {\n total,\n completed,\n percent: total === 0 ? 0 : Math.round((completed / total) * 100),\n };\n}\n\n/** The course after a lesson was completed, progress recomputed. */\nexport function markLessonCompleted(course: Course, lessonId: string): Course {\n const sections = course.sections.map((section) => ({\n ...section,\n items: section.items.map((item) =>\n item.id === lessonId && item.kind === \"lesson\"\n ? { ...item, completed: true }\n : item,\n ),\n }));\n const progress = computeProgress({ sections });\n return {\n ...course,\n sections,\n totalLessons: progress.total,\n completedLessons: progress.completed,\n progressPercent: progress.percent,\n };\n}\n\n/**\n * Item to show first: the requested one when it exists, else the one to\n * resume with, else the first unlocked item.\n */\nexport function resolveInitialItemId(\n course: Course,\n requestedId?: string,\n): string | undefined {\n if (findItem(course, requestedId) != null) {\n return requestedId;\n }\n if (findItem(course, course.resumeItemId)?.locked === false) {\n return course.resumeItemId;\n }\n return flattenItems(course).find((item) => !item.locked)?.id;\n}\n\n/** `{name}` placeholders of a text. Unknown placeholders stay untouched. */\nexport function fillTemplate(\n template: string,\n values: Record<string, string | number>,\n): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, key: string) =>\n key in values ? String(values[key]) : match,\n );\n}\n\nexport function isPlaybackComplete(\n positionSeconds: number,\n durationSeconds: number,\n): boolean {\n return (\n durationSeconds > 0 &&\n positionSeconds / durationSeconds >= COMPLETE_AT_FRACTION\n );\n}\n\n/** Whether a periodic save is due. */\nexport function isSaveDue(\n lastSavedAt: number | undefined,\n now: number,\n intervalMs: number = PROGRESS_SAVE_INTERVAL_MS,\n): boolean {\n return lastSavedAt == null || now - lastSavedAt >= intervalMs;\n}\n\n/** Where to resume: nothing when the video was (nearly) watched to the end. */\nexport function resumePosition(\n positionSeconds: number,\n durationSeconds: number,\n): number {\n if (positionSeconds < 5) {\n return 0;\n }\n if (durationSeconds > 0 && positionSeconds >= durationSeconds - 5) {\n return 0;\n }\n return Math.floor(positionSeconds);\n}\n\n/** `m:ss`, or `h:mm:ss` from an hour on. */\nexport function formatDuration(totalSeconds: number): string {\n const seconds = Math.max(0, Math.floor(totalSeconds));\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = String(seconds % 60).padStart(2, \"0\");\n return h > 0 ? `${h}:${String(m).padStart(2, \"0\")}:${s}` : `${m}:${s}`;\n}\n\nexport function formatBytes(bytes: number): string {\n if (!(bytes > 0)) {\n return \"0 B\";\n }\n const units = [\"B\", \"KB\", \"MB\", \"GB\"];\n const exponent = Math.min(\n Math.floor(Math.log(bytes) / Math.log(1024)),\n units.length - 1,\n );\n const value = bytes / 1024 ** exponent;\n return `${exponent === 0 || value >= 10 ? Math.round(value) : value.toFixed(1)} ${units[exponent]}`;\n}\n\n// Tests\n\n/** The empty answer of a question, with the order of sort items as given. */\nexport function emptyAnswer(config: TestQuestionConfig): QuestionAnswer {\n switch (config.type) {\n case \"mcq\":\n return { type: \"mcq\", selectedIds: [] };\n case \"todolist\":\n return { type: \"todolist\", checkedIds: [] };\n case \"fill_gap\":\n return { type: \"fill_gap\", values: {} };\n case \"sort_order\":\n return {\n type: \"sort_order\",\n orderedIds: config.items.map((item) => item.id),\n };\n }\n}\n\n/** Whether the customer did anything with the question. */\nexport function isAnswered(\n question: TestQuestion,\n answer: QuestionAnswer | undefined,\n): boolean {\n if (answer == null || answer.type !== question.config.type) {\n return false;\n }\n switch (answer.type) {\n case \"mcq\":\n return answer.selectedIds.length > 0;\n case \"todolist\":\n return answer.checkedIds.length > 0;\n case \"fill_gap\":\n return Object.values(answer.values).some((value) => value.trim() !== \"\");\n case \"sort_order\":\n // The view stores a sort answer once the customer moved an item.\n return answer.orderedIds.length > 0;\n }\n}\n\nexport function countUnanswered(\n questions: TestQuestion[],\n answers: TestAnswers,\n): number {\n return questions.filter((q) => !isAnswered(q, answers[q.id])).length;\n}\n\n/** Moves the element at `from` to `to`; out of range moves change nothing. */\nexport function moveItem<T>(list: readonly T[], from: number, to: number): T[] {\n if (\n from === to ||\n from < 0 ||\n to < 0 ||\n from >= list.length ||\n to >= list.length\n ) {\n return [...list];\n }\n const copy = [...list];\n const [item] = copy.splice(from, 1);\n copy.splice(to, 0, item!);\n return copy;\n}\n\nexport type GapSegment =\n | { type: \"text\"; text: string }\n | { type: \"blank\"; id: string };\n\n/**\n * Splits a fill-in-the-gap template (`Paris is in {{g1}}`) into text and\n * blanks. Placeholders that are not in `blanks` stay text.\n */\nexport function splitGapTemplate(\n template: string,\n blanks: readonly string[],\n): GapSegment[] {\n const segments: GapSegment[] = [];\n const pattern = /\\{\\{\\s*([^{}\\s]+)\\s*\\}\\}/g;\n let last = 0;\n for (const match of template.matchAll(pattern)) {\n const id = match[1]!;\n if (!blanks.includes(id)) {\n continue;\n }\n if (match.index > last) {\n segments.push({ type: \"text\", text: template.slice(last, match.index) });\n }\n segments.push({ type: \"blank\", id });\n last = match.index + match[0].length;\n }\n if (last < template.length) {\n segments.push({ type: \"text\", text: template.slice(last) });\n }\n return segments;\n}\n\n/** A readable form of an answer, used on the result screen. */\nexport function describeAnswer(\n question: TestQuestion,\n answer: QuestionAnswer | undefined,\n): string {\n const { config } = question;\n if (answer == null || answer.type !== config.type) {\n return \"\";\n }\n const label = (list: { id: string; label: string }[], id: string) =>\n list.find((entry) => entry.id === id)?.label ?? id;\n\n if (config.type === \"mcq\" && answer.type === \"mcq\") {\n return answer.selectedIds.map((id) => label(config.choices, id)).join(\", \");\n }\n if (config.type === \"todolist\" && answer.type === \"todolist\") {\n return answer.checkedIds.map((id) => label(config.steps, id)).join(\", \");\n }\n if (config.type === \"sort_order\" && answer.type === \"sort_order\") {\n return answer.orderedIds.map((id) => label(config.items, id)).join(\" → \");\n }\n if (config.type === \"fill_gap\" && answer.type === \"fill_gap\") {\n return splitGapTemplate(config.template, config.blanks)\n .map((segment) =>\n segment.type === \"text\"\n ? segment.text\n : `[${answer.values[segment.id] ?? \"\"}]`,\n )\n .join(\"\");\n }\n return \"\";\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/emems/.t3/worktrees/bitsnap-react/emems-chat-v1/dist/course.js","../src/course/PanelCourse.tsx","../src/course/accent.ts","../src/course/analytics.ts","../src/course/context.tsx","../src/course/CourseOutline.tsx","../src/course/logic.ts"],"names":["accentVars","colors","vars","brand","brandDark","foreground","readableForeground","CourseViewContext","createContext","CourseViewProvider","useCourseView","value","useContext","useCourseTrack","analytics","preview","useCallback","event","props","flattenItems","course","section","item","findItem","itemId","getAdjacentItems","items","index","getAutoplayTarget","next","computeProgress","total","completed","markLessonCompleted","lessonId","sections","progress","resolveInitialItemId","requestedId","fillTemplate","template","values","match","key","isPlaybackComplete","positionSeconds","durationSeconds","resumePosition","formatDuration","totalSeconds","seconds","h","m","s"],"mappings":"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","file":"/home/emems/.t3/worktrees/bitsnap-react/emems-chat-v1/dist/course.js","sourcesContent":[null,"import { ChevronLeft, List, Play } from \"lucide-react\";\nimport {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n InstrumentProvider,\n useInstrument,\n type AnalyticsOption,\n} from \"../analytics/instrument\";\nimport { usePanelConfig } from \"../panel/PanelProvider\";\nimport type { GetThemeOutput } from \"../panel/types\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../ui/button\";\nimport { accentVars } from \"./accent\";\nimport { useCourseTrack } from \"./analytics\";\nimport { CourseViewProvider, useCourseView } from \"./context\";\nimport { CourseOutline } from \"./CourseOutline\";\nimport { createRpcCourseDataSource } from \"./rpc\";\nimport { LessonPane } from \"./LessonPane\";\nimport {\n fillTemplate,\n findItem,\n flattenItems,\n getAdjacentItems,\n getAutoplayTarget,\n markLessonCompleted,\n resolveInitialItemId,\n type FlatItem,\n} from \"./logic\";\nimport { getCourseTexts, type CourseTexts } from \"./texts\";\nimport { TestPane } from \"./TestPane\";\nimport {\n CourseError,\n type Course,\n type CourseDataSource,\n type ItemRef,\n type Lesson,\n type ProgressSaved,\n} from \"./types\";\nimport { CARD, ErrorBox, LoadingBlock, ProgressBar, errorMessage } from \"./ui\";\nimport { useAsyncResource } from \"./useAsyncResource\";\n\nexport type PanelCourseProps = {\n /** `UserProduct.access_id` of the purchased course. Not needed with `dataSource`. */\n accessId?: string;\n /** Panel session token; read from the panel session when omitted. */\n accessToken?: string;\n /**\n * Replaces the default data source (`UserPanelCourseService` through the\n * panel's transport). The creator preview of the dashboard passes its own.\n */\n dataSource?: CourseDataSource;\n /** Creator preview: shows a badge and never saves progress. */\n preview?: boolean;\n /** The open lesson or test. Controlled together with `onItemChange`. */\n itemId?: string;\n onItemChange?: (itemId: string) => void;\n /** Shows a back button. */\n onBack?: () => void;\n /** Course structure to render right away (server rendering, preview). */\n initialCourse?: Course;\n /** Text overrides; Polish and English defaults by `locale`. */\n texts?: Partial<CourseTexts>;\n /** Defaults to the locale of the library (`setLocale`). */\n locale?: string;\n /** Brand colors; the panel theme (`setTheme`) by default. */\n theme?: GetThemeOutput;\n className?: string;\n /**\n * Usage statistics for your Bitsnap dashboard (course opened, lessons\n * started and completed, video progress, tests, ratings). On by default;\n * `false` turns it off, and the creator `preview` never reports. Inside\n * `<Panel>` the panel's setting applies unless this is given. An object\n * lets you forward every event: `{ onEvent }`. Nothing is stored on the\n * visitor's device.\n */\n analytics?: AnalyticsOption;\n};\n\nconst AUTOPLAY_KEY = \"bitsnap-course-autoplay\";\n\nfunction useAutoplayPreference(): [boolean, (value: boolean) => void] {\n const [value, setValue] = useState(false);\n useEffect(() => {\n try {\n setValue(window.localStorage.getItem(AUTOPLAY_KEY) === \"1\");\n } catch {\n // Storage is not available (private mode); keep the default.\n }\n }, []);\n const update = useCallback((next: boolean) => {\n setValue(next);\n try {\n window.localStorage.setItem(AUTOPLAY_KEY, next ? \"1\" : \"0\");\n } catch {\n // Not persisted.\n }\n }, []);\n return [value, update];\n}\n\nfunction toRef(item: FlatItem | undefined): ItemRef | undefined {\n return item == null ? undefined : { id: item.id, kind: item.kind };\n}\n\nfunction LockedPane({ item }: { item: FlatItem }) {\n const { texts } = useCourseView();\n return (\n <div className={cn(CARD, \"space-y-2\")} role=\"status\">\n <h2 className=\"text-2xl font-semibold break-words\">{item.title}</h2>\n <p className=\"text-sm text-neutral-600 dark:text-neutral-300\">\n {item.lockReason\n ? fillTemplate(texts.lockedPage, {\n test: item.lockReason.gatingTestTitle,\n })\n : texts.locked}\n </p>\n </div>\n );\n}\n\ntype LessonContainerProps = {\n course: Course;\n item: FlatItem;\n autoplay: boolean;\n onAutoplayChange: (value: boolean) => void;\n startPlaying: boolean;\n onNavigate: (ref: ItemRef) => void;\n onProgressSaved: (lessonId: string, saved: ProgressSaved) => void;\n onEnded: () => void;\n onLocked: () => void;\n mobileOutline: ReactNode;\n};\n\nfunction LessonContainer({\n course,\n item,\n autoplay,\n onAutoplayChange,\n startPlaying,\n onNavigate,\n onProgressSaved,\n onEnded,\n onLocked,\n mobileOutline,\n}: LessonContainerProps) {\n const { dataSource, texts } = useCourseView();\n const lesson = useAsyncResource<Lesson>(\n () => dataSource.getLesson(item.id),\n [dataSource, item.id],\n );\n\n const lockedByServer =\n lesson.status === \"error\" &&\n lesson.error instanceof CourseError &&\n lesson.error.code === \"item_locked\";\n useEffect(() => {\n // The structure on screen is stale: take the locks from the server again.\n if (lockedByServer) onLocked();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [lockedByServer]);\n\n if (lesson.status === \"loading\") {\n return <LoadingBlock />;\n }\n if (lesson.status === \"error\") {\n return lockedByServer ? (\n <div className={cn(CARD, \"space-y-2\")} role=\"alert\">\n <h2 className=\"text-2xl font-semibold break-words\">{item.title}</h2>\n <p className=\"text-sm\">{errorMessage(lesson.error, texts)}</p>\n </div>\n ) : (\n <ErrorBox error={lesson.error} onRetry={lesson.reload} />\n );\n }\n\n const adjacent = getAdjacentItems(course, item.id);\n const position =\n flattenItems(course)\n .filter((entry) => entry.kind !== \"test\")\n .findIndex((entry) => entry.id === item.id) + 1;\n return (\n <LessonPane\n key={lesson.data.id}\n lesson={lesson.data}\n position={position}\n mobileOutline={mobileOutline}\n nextItem={findItem(\n course,\n (lesson.data.next ?? toRef(adjacent.next))?.id,\n )}\n courseType={course.type}\n prev={lesson.data.prev ?? toRef(adjacent.prev)}\n next={lesson.data.next ?? toRef(adjacent.next)}\n autoplay={autoplay}\n onAutoplayChange={onAutoplayChange}\n startPlaying={startPlaying}\n onNavigate={onNavigate}\n onProgressSaved={onProgressSaved}\n onEnded={onEnded}\n onUrlsExpired={lesson.refresh}\n />\n );\n}\n\nfunction CourseView({\n course,\n selectedId,\n onSelect,\n onBack,\n onCourseChange,\n onCourseRefresh,\n}: {\n course: Course;\n selectedId?: string;\n onSelect: (itemId: string) => void;\n onBack?: () => void;\n onCourseChange: (update: (course: Course) => Course) => void;\n onCourseRefresh: () => void;\n}) {\n const { texts, preview } = useCourseView();\n const track = useCourseTrack();\n const rootRef = useRef<HTMLDivElement>(null);\n const contentsId = useId();\n const [contentsOpen, setContentsOpen] = useState(false);\n const [autoplay, setAutoplay] = useAutoplayPreference();\n const [playOnOpen, setPlayOnOpen] = useState<string>();\n\n const currentId = resolveInitialItemId(course, selectedId);\n const item = findItem(course, currentId);\n\n // Once per opening of the course.\n useEffect(() => {\n track(\"course_opened\", {});\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [course.id]);\n\n const open = (itemId: string, play = false) => {\n setPlayOnOpen(play ? itemId : undefined);\n setContentsOpen(false);\n onSelect(itemId);\n rootRef.current?.scrollIntoView({ block: \"start\", behavior: \"smooth\" });\n };\n\n const outline = (\n <CourseOutline\n course={course}\n selectedId={currentId}\n variant={course.type === \"video\" ? \"playlist\" : \"modules\"}\n onSelect={(entry) => open(entry.id)}\n />\n );\n\n let content;\n if (item == null) {\n content = <p className=\"text-sm text-neutral-500\">{texts.noItem}</p>;\n } else if (item.locked) {\n content = <LockedPane item={item} />;\n } else if (item.kind === \"test\") {\n content = (\n <TestPane\n key={item.id}\n testId={item.id}\n title={item.title}\n onSubmitted={() => onCourseRefresh()}\n />\n );\n } else {\n content = (\n <LessonContainer\n key={item.id}\n course={course}\n item={item}\n autoplay={autoplay}\n onAutoplayChange={setAutoplay}\n startPlaying={playOnOpen === item.id}\n onNavigate={(ref) => open(ref.id)}\n onProgressSaved={(lessonId, saved) =>\n onCourseChange((current) => ({\n ...(saved.completed\n ? markLessonCompleted(current, lessonId)\n : current),\n completedLessons: saved.completedLessons,\n progressPercent: saved.progressPercent,\n }))\n }\n onEnded={() => {\n if (!autoplay) return;\n const target = getAutoplayTarget(course, item.id);\n if (target) open(target.id, true);\n }}\n onLocked={onCourseRefresh}\n mobileOutline={outline}\n />\n );\n }\n\n const isVideo = course.type === \"video\";\n const showMobileContents =\n !isVideo || item == null || item.locked || item.kind === \"test\";\n const showResume =\n course.resumeItemId !== \"\" &&\n course.resumeItemId !== currentId &&\n findItem(course, course.resumeItemId)?.locked === false;\n\n return (\n <div ref={rootRef} className=\"space-y-4 scroll-mt-4\">\n <header className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n {preview && (\n <span\n data-course-preview\n className=\"rounded-full bg-amber-100 px-3 py-0.5 text-xs font-semibold text-amber-900 dark:bg-amber-900/40 dark:text-amber-200\"\n >\n {texts.previewBadge}\n </span>\n )}\n </div>\n <div className=\"flex flex-wrap items-end justify-between gap-4\">\n <div className=\"min-w-0 space-y-1\">\n <h1 className=\"text-2xl font-semibold break-words sm:text-3xl\">\n {course.title}\n </h1>\n </div>\n {showResume && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => open(course.resumeItemId)}\n >\n <Play aria-hidden />\n {texts.continueCourse}\n </Button>\n )}\n </div>\n <div className=\"max-w-xl space-y-1 md:max-w-none\">\n {course.progressPercent >= 100 && (\n <p\n role=\"status\"\n className=\"font-semibold text-emerald-700 dark:text-emerald-400\"\n >\n {texts.courseCompleted ?? texts.completed}\n </p>\n )}\n <ProgressBar\n percent={course.progressPercent}\n label={fillTemplate(texts.progressPercent, {\n percent: course.progressPercent,\n })}\n />\n <p className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {fillTemplate(texts.progressLessons, {\n done: course.completedLessons,\n total: course.totalLessons,\n })}\n {\" · \"}\n {fillTemplate(texts.progressPercent, {\n percent: course.progressPercent,\n })}\n </p>\n </div>\n </header>\n\n <div\n className={cn(\n \"grid gap-6\",\n isVideo\n ? \"lg:grid-cols-[minmax(0,1fr)_380px]\"\n : \"lg:grid-cols-[320px_minmax(0,1fr)]\",\n )}\n >\n {showMobileContents && (\n <div className=\"lg:hidden\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"min-h-11\"\n aria-controls={contentsId}\n aria-expanded={contentsOpen}\n onClick={() => setContentsOpen((value) => !value)}\n >\n <List aria-hidden />\n {texts.contents}\n </Button>\n </div>\n )}\n\n {showMobileContents && (\n <aside\n id={contentsId}\n className={cn(\n CARD,\n isVideo && \"lg:hidden\",\n \"p-3 sm:p-3 lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto\",\n contentsOpen ? \"block\" : \"hidden\",\n !isVideo && \"lg:block\",\n )}\n >\n {outline}\n </aside>\n )}\n\n <div className=\"min-w-0\" data-course-content>\n {content}\n </div>\n\n {isVideo && (\n <aside\n className={cn(\n CARD,\n \"hidden p-3 sm:p-3 lg:block lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto\",\n )}\n >\n {outline}\n </aside>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The course of a customer inside the panel: structure with progress and\n * locks, lessons with video, text and attachments, tests, questions and\n * ratings. Standard courses get a sidebar of modules, video courses a\n * playlist next to the player.\n *\n * Load it lazily from `bitsnap-react/course`. Inside the panel it needs only\n * `accessId` of the purchased course.\n */\nexport function PanelCourse({\n accessId,\n accessToken,\n dataSource: customDataSource,\n preview = false,\n itemId,\n onItemChange,\n onBack,\n initialCourse,\n texts: textOverrides,\n locale,\n theme,\n className,\n analytics,\n}: PanelCourseProps) {\n const panel = usePanelConfig();\n // The creator preview reports nothing, not even the page view of the page it sits on.\n const instrument = useInstrument(panel.projectID, preview ? false : analytics, \"course\");\n const texts = useMemo(\n () => getCourseTexts(textOverrides, locale),\n [textOverrides, locale],\n );\n const dataSource = useMemo(() => {\n if (customDataSource) return customDataSource;\n if (accessId == null || accessId === \"\") {\n throw new Error(\"PanelCourse needs an accessId or a dataSource\");\n }\n return createRpcCourseDataSource({ accessId, accessToken });\n }, [customDataSource, accessId, accessToken]);\n\n const course = useAsyncResource<Course>(\n () => dataSource.getCourse(),\n [dataSource],\n initialCourse,\n );\n\n const [internalItemId, setInternalItemId] = useState<string>();\n // With `onItemChange` the owner navigates, so no `itemId` means the default\n // item, not the one picked last.\n const selectedId = onItemChange ? itemId : (itemId ?? internalItemId);\n const select = useCallback(\n (next: string) => {\n setInternalItemId(next);\n onItemChange?.(next);\n },\n [onItemChange],\n );\n\n const style = accentVars(theme?.colors ?? panel.theme?.colors);\n\n const courseId = course.status === \"success\" ? course.data.id : undefined;\n const viewValue = useMemo(\n () => ({\n dataSource,\n texts,\n preview,\n analytics: { courseId, instrument },\n }),\n [dataSource, texts, preview, courseId, instrument],\n );\n\n return (\n <InstrumentProvider value={instrument}>\n <CourseViewProvider value={viewValue}>\n <div\n data-bitsnap-course\n data-course-type={course.status === \"success\" ? course.data.type : undefined}\n style={style}\n className={cn(\"font-inter w-full max-w-7xl\", className)}\n >\n {course.status === \"loading\" && (\n <div className=\"space-y-4\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n <p className=\"sr-only\" role=\"status\">\n {texts.loading}\n </p>\n <LoadingBlock />\n </div>\n )}\n {course.status === \"error\" && (\n <div className=\"space-y-4\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n <ErrorBox error={course.error} onRetry={course.reload} />\n </div>\n )}\n {course.status === \"success\" && (\n <CourseView\n course={course.data}\n selectedId={selectedId}\n onSelect={select}\n onBack={onBack}\n onCourseChange={course.mutate}\n onCourseRefresh={course.refresh}\n />\n )}\n </div>\n </CourseViewProvider>\n </InstrumentProvider>\n );\n}\n\nexport default PanelCourse;\n","import type { CSSProperties } from \"react\";\nimport { readableForeground } from \"../components/returns/color\";\n\n/**\n * CSS variables of the accent (brand) color of the course view. Without a\n * brand color nothing is set and the library's `--primary` applies.\n */\nexport function accentVars(colors?: {\n brand?: string;\n brandDark?: string;\n}): CSSProperties {\n const vars: Record<string, string> = {};\n const brand = colors?.brand?.trim();\n const brandDark = colors?.brandDark?.trim() || brand;\n if (brand) {\n vars[\"--course-accent\"] = brand;\n const foreground = readableForeground(brand);\n if (foreground) vars[\"--course-accent-fg\"] = foreground;\n }\n if (brandDark) {\n vars[\"--course-accent-dark\"] = brandDark;\n const foreground = readableForeground(brandDark);\n if (foreground) vars[\"--course-accent-dark-fg\"] = foreground;\n }\n return vars as CSSProperties;\n}\n","import { useCallback } from \"react\";\nimport type { EventProps } from \"@/src/analytics/contract\";\nimport { useCourseView } from \"./context\";\n\ntype CourseEventName =\n | \"course_opened\"\n | \"lesson_started\"\n | \"lesson_completed\"\n | \"video_progress\"\n | \"test_submitted\"\n | \"lesson_rated\";\n\n/**\n * Reports a course event; `course_id` is added. Nothing is reported in the\n * creator preview, before the course has loaded, or without analytics.\n */\nexport function useCourseTrack() {\n const { analytics, preview } = useCourseView();\n return useCallback(\n <E extends CourseEventName>(\n event: E,\n props: Omit<EventProps<E>, \"course_id\">,\n ) => {\n if (preview || analytics?.courseId == null) {\n return;\n }\n analytics.instrument.track(event, {\n ...props,\n course_id: analytics.courseId,\n } as unknown as EventProps<E>);\n },\n [analytics, preview],\n );\n}\n","import { createContext, useContext } from \"react\";\nimport type { Instrument } from \"@/src/analytics/instrument\";\nimport type { CourseTexts } from \"./texts\";\nimport type { CourseDataSource } from \"./types\";\n\nexport type CourseAnalytics = {\n /** `undefined` until the course has loaded. */\n courseId?: string;\n instrument: Instrument;\n};\n\nexport type CourseViewContextValue = {\n dataSource: CourseDataSource;\n texts: CourseTexts;\n /** Creator preview: nothing the viewer does is saved as progress. */\n preview: boolean;\n /** Usage statistics; none without it. */\n analytics?: CourseAnalytics;\n};\n\nconst CourseViewContext = createContext<CourseViewContextValue | undefined>(\n undefined,\n);\n\nexport const CourseViewProvider = CourseViewContext.Provider;\n\nexport function useCourseView(): CourseViewContextValue {\n const value = useContext(CourseViewContext);\n if (value == null) {\n throw new Error(\"Course components have to be rendered inside PanelCourse\");\n }\n return value;\n}\n","import {\n CheckCircle2,\n ClipboardCheck,\n Lock,\n PlayCircle,\n FileText,\n} from \"lucide-react\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useCourseView } from \"./context\";\nimport { fillTemplate, formatDuration } from \"./logic\";\nimport type { Course, CourseItem } from \"./types\";\n\ntype CourseOutlineProps = {\n course: Course;\n selectedId?: string;\n onSelect: (item: CourseItem) => void;\n /**\n * `playlist` (video courses) shows the thumbnail and duration of every\n * video; `modules` (standard courses) is a plain list of lessons.\n */\n variant: \"modules\" | \"playlist\";\n className?: string;\n};\n\nfunction ItemIcon({ item }: { item: CourseItem }) {\n if (item.locked) {\n return <Lock className=\"size-4 text-neutral-500\" aria-hidden />;\n }\n if (item.completed) {\n return (\n <CheckCircle2\n className=\"size-4 text-emerald-600 dark:text-emerald-400\"\n aria-hidden\n />\n );\n }\n if (item.kind === \"test\") {\n return <ClipboardCheck className=\"size-4 text-neutral-500\" aria-hidden />;\n }\n return item.durationSeconds > 0 ? (\n <PlayCircle className=\"size-4 text-neutral-500\" aria-hidden />\n ) : (\n <FileText className=\"size-4 text-neutral-500\" aria-hidden />\n );\n}\n\nfunction OutlineItem({\n item,\n selected,\n variant,\n onSelect,\n}: {\n item: CourseItem;\n selected: boolean;\n variant: \"modules\" | \"playlist\";\n onSelect: (item: CourseItem) => void;\n}) {\n const { texts } = useCourseView();\n const lockText = item.lockReason\n ? fillTemplate(texts.lockedByTest, {\n test: item.lockReason.gatingTestTitle,\n })\n : texts.locked;\n const state = item.locked\n ? lockText\n : item.completed\n ? texts.completed\n : undefined;\n\n return (\n <li>\n <button\n type=\"button\"\n data-item-id={item.id}\n data-locked={item.locked || undefined}\n aria-current={selected ? \"true\" : undefined}\n aria-disabled={item.locked || undefined}\n title={item.locked ? lockText : undefined}\n onClick={() => {\n if (!item.locked) onSelect(item);\n }}\n className={cn(\n \"flex min-h-11 w-full items-start gap-3 rounded-lg p-2 text-left text-sm outline-none transition focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n selected\n ? \"bg-neutral-200 dark:bg-neutral-700\"\n : \"hover:bg-neutral-100 dark:hover:bg-neutral-800\",\n item.locked &&\n \"cursor-not-allowed opacity-60 hover:bg-transparent dark:hover:bg-transparent\",\n )}\n >\n {variant === \"playlist\" && item.kind === \"lesson\" && (\n <span className=\"relative aspect-video w-24 shrink-0 overflow-hidden rounded bg-neutral-200 dark:bg-neutral-800\">\n {item.thumbnailUrl && (\n <img\n src={item.thumbnailUrl}\n alt=\"\"\n loading=\"lazy\"\n draggable={false}\n className=\"size-full object-cover\"\n />\n )}\n {item.durationSeconds > 0 && (\n <span className=\"absolute right-1 bottom-1 rounded bg-black/75 px-1 text-[10px] text-white\">\n {formatDuration(item.durationSeconds)}\n </span>\n )}\n </span>\n )}\n {(variant === \"modules\" || item.kind === \"test\") && (\n <span className=\"mt-0.5 shrink-0\">\n <ItemIcon item={item} />\n </span>\n )}\n <span className=\"min-w-0 flex-1\">\n <span className=\"block font-medium break-words\">{item.title}</span>\n <span className=\"mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-neutral-500 dark:text-neutral-400\">\n {item.kind === \"test\" && <span>{texts.testBadge}</span>}\n {item.isFree && !item.locked && <span>{texts.free}</span>}\n {variant === \"modules\" && item.durationSeconds > 0 && (\n <span>{formatDuration(item.durationSeconds)}</span>\n )}\n {state && <span>{state}</span>}\n </span>\n </span>\n {variant === \"playlist\" && item.kind === \"lesson\" && (\n <span className=\"mt-0.5 shrink-0\">\n <ItemIcon item={item} />\n </span>\n )}\n </button>\n </li>\n );\n}\n\n/** Modules / sections with their lessons and tests. */\nexport function CourseOutline({\n course,\n selectedId,\n onSelect,\n variant,\n className,\n}: CourseOutlineProps) {\n const { texts } = useCourseView();\n const outlineId = useId();\n const rootRef = useRef<HTMLElement>(null);\n const currentSection = course.sections.find((section) =>\n section.items.some((item) => item.id === selectedId),\n )?.id;\n const [openSections, setOpenSections] = useState<Set<string>>(\n () => new Set(currentSection ? [currentSection] : []),\n );\n useEffect(() => {\n if (currentSection)\n setOpenSections((current) => new Set([...current, currentSection]));\n }, [currentSection]);\n useEffect(() => {\n const selected =\n rootRef.current?.querySelector<HTMLElement>(\"[aria-current]\");\n if (selected && !selected.closest(\"[hidden]\"))\n selected.scrollIntoView?.({ block: \"nearest\" });\n }, [selectedId, openSections]);\n\n const renderItems = (items: CourseItem[]) => (\n <ul className=\"flex flex-col gap-1\">\n {items.map((item) => (\n <OutlineItem\n key={item.id}\n item={item}\n selected={item.id === selectedId}\n variant={variant}\n onSelect={onSelect}\n />\n ))}\n </ul>\n );\n\n return (\n <nav\n ref={rootRef}\n aria-label={texts.contents}\n className={cn(\"space-y-4\", className)}\n >\n {course.sections.map((section) => {\n const lessons = section.items.filter((i) => i.kind === \"lesson\");\n const done = lessons.filter((i) => i.completed).length;\n return (\n <section key={section.id} data-section-id={section.id}>\n <h3 className=\"mb-1 text-sm font-semibold\">\n <button\n type=\"button\"\n className=\"flex min-h-11 w-full items-center justify-between gap-2 rounded-lg px-2 text-left focus-visible:outline focus-visible:outline-2\"\n aria-expanded={openSections.has(section.id)}\n aria-controls={`${outlineId}-${section.id}`}\n onClick={() =>\n setOpenSections((current) => {\n const next = new Set(current);\n if (next.has(section.id)) next.delete(section.id);\n else next.add(section.id);\n return next;\n })\n }\n >\n <span className=\"break-words\">{section.title}</span>\n {lessons.length > 0 && (\n <span className=\"shrink-0 text-xs font-normal text-neutral-500 dark:text-neutral-400\">\n {done}/{lessons.length}\n </span>\n )}\n </button>\n </h3>\n <div\n id={`${outlineId}-${section.id}`}\n hidden={!openSections.has(section.id)}\n >\n {renderItems(section.items)}\n </div>\n </section>\n );\n })}\n {course.finalItems.length > 0 && (\n <section data-section-id=\"final\">\n {renderItems(course.finalItems)}\n </section>\n )}\n </nav>\n );\n}\n","import type {\n Course,\n CourseItem,\n QuestionAnswer,\n TestAnswers,\n TestQuestion,\n TestQuestionConfig,\n} from \"./types\";\n\n/** Playback share from which a lesson counts as watched. */\nexport const COMPLETE_AT_FRACTION = 0.8;\n\n/** How often the player reports the position while it plays. */\nexport const PROGRESS_SAVE_INTERVAL_MS = 15_000;\n\n/** An item together with the section it sits in (empty for final tests). */\nexport type FlatItem = CourseItem & { sectionId: string };\n\n/** All items of the course in the order they are taken. */\nexport function flattenItems(course: Course): FlatItem[] {\n return [\n ...course.sections.flatMap((section) =>\n section.items.map((item) => ({ ...item, sectionId: section.id })),\n ),\n ...course.finalItems.map((item) => ({ ...item, sectionId: \"\" })),\n ];\n}\n\nexport function findItem(\n course: Course,\n itemId: string | undefined,\n): FlatItem | undefined {\n if (itemId == null || itemId === \"\") {\n return undefined;\n }\n return flattenItems(course).find((item) => item.id === itemId);\n}\n\nexport function getAdjacentItems(\n course: Course,\n itemId: string,\n): { prev?: FlatItem; next?: FlatItem } {\n const items = flattenItems(course);\n const index = items.findIndex((item) => item.id === itemId);\n if (index < 0) {\n return {};\n }\n return { prev: items[index - 1], next: items[index + 1] };\n}\n\n/**\n * The item to start autoplay with after `itemId` ended: the next item when it\n * is an unlocked lesson. A test or a locked lesson stops the playlist.\n */\nexport function getAutoplayTarget(\n course: Course,\n itemId: string,\n): FlatItem | undefined {\n const { next } = getAdjacentItems(course, itemId);\n return next != null && next.kind === \"lesson\" && !next.locked\n ? next\n : undefined;\n}\n\n/** Progress counted from the lessons of the structure (tests do not count). */\nexport function computeProgress(course: Pick<Course, \"sections\">): {\n total: number;\n completed: number;\n percent: number;\n} {\n let total = 0;\n let completed = 0;\n for (const section of course.sections) {\n for (const item of section.items) {\n if (item.kind !== \"lesson\") {\n continue;\n }\n total += 1;\n if (item.completed) {\n completed += 1;\n }\n }\n }\n return {\n total,\n completed,\n percent: total === 0 ? 0 : Math.round((completed / total) * 100),\n };\n}\n\n/** The course after a lesson was completed, progress recomputed. */\nexport function markLessonCompleted(course: Course, lessonId: string): Course {\n const sections = course.sections.map((section) => ({\n ...section,\n items: section.items.map((item) =>\n item.id === lessonId && item.kind === \"lesson\"\n ? { ...item, completed: true }\n : item,\n ),\n }));\n const progress = computeProgress({ sections });\n return {\n ...course,\n sections,\n totalLessons: progress.total,\n completedLessons: progress.completed,\n progressPercent: progress.percent,\n };\n}\n\n/**\n * Item to show first: the requested one when it exists, else the one to\n * resume with, else the first unlocked item.\n */\nexport function resolveInitialItemId(\n course: Course,\n requestedId?: string,\n): string | undefined {\n if (findItem(course, requestedId) != null) {\n return requestedId;\n }\n if (findItem(course, course.resumeItemId)?.locked === false) {\n return course.resumeItemId;\n }\n return flattenItems(course).find((item) => !item.locked)?.id;\n}\n\n/** `{name}` placeholders of a text. Unknown placeholders stay untouched. */\nexport function fillTemplate(\n template: string,\n values: Record<string, string | number>,\n): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, key: string) =>\n key in values ? String(values[key]) : match,\n );\n}\n\nexport function isPlaybackComplete(\n positionSeconds: number,\n durationSeconds: number,\n): boolean {\n return (\n durationSeconds > 0 &&\n positionSeconds / durationSeconds >= COMPLETE_AT_FRACTION\n );\n}\n\n/** Whether a periodic save is due. */\nexport function isSaveDue(\n lastSavedAt: number | undefined,\n now: number,\n intervalMs: number = PROGRESS_SAVE_INTERVAL_MS,\n): boolean {\n return lastSavedAt == null || now - lastSavedAt >= intervalMs;\n}\n\n/** Where to resume: nothing when the video was (nearly) watched to the end. */\nexport function resumePosition(\n positionSeconds: number,\n durationSeconds: number,\n): number {\n if (positionSeconds < 5) {\n return 0;\n }\n if (durationSeconds > 0 && positionSeconds >= durationSeconds - 5) {\n return 0;\n }\n return Math.floor(positionSeconds);\n}\n\n/** `m:ss`, or `h:mm:ss` from an hour on. */\nexport function formatDuration(totalSeconds: number): string {\n const seconds = Math.max(0, Math.floor(totalSeconds));\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = String(seconds % 60).padStart(2, \"0\");\n return h > 0 ? `${h}:${String(m).padStart(2, \"0\")}:${s}` : `${m}:${s}`;\n}\n\nexport function formatBytes(bytes: number): string {\n if (!(bytes > 0)) {\n return \"0 B\";\n }\n const units = [\"B\", \"KB\", \"MB\", \"GB\"];\n const exponent = Math.min(\n Math.floor(Math.log(bytes) / Math.log(1024)),\n units.length - 1,\n );\n const value = bytes / 1024 ** exponent;\n return `${exponent === 0 || value >= 10 ? Math.round(value) : value.toFixed(1)} ${units[exponent]}`;\n}\n\n// Tests\n\n/** The empty answer of a question, with the order of sort items as given. */\nexport function emptyAnswer(config: TestQuestionConfig): QuestionAnswer {\n switch (config.type) {\n case \"mcq\":\n return { type: \"mcq\", selectedIds: [] };\n case \"todolist\":\n return { type: \"todolist\", checkedIds: [] };\n case \"fill_gap\":\n return { type: \"fill_gap\", values: {} };\n case \"sort_order\":\n return {\n type: \"sort_order\",\n orderedIds: config.items.map((item) => item.id),\n };\n }\n}\n\n/** Whether the customer did anything with the question. */\nexport function isAnswered(\n question: TestQuestion,\n answer: QuestionAnswer | undefined,\n): boolean {\n if (answer == null || answer.type !== question.config.type) {\n return false;\n }\n switch (answer.type) {\n case \"mcq\":\n return answer.selectedIds.length > 0;\n case \"todolist\":\n return answer.checkedIds.length > 0;\n case \"fill_gap\":\n return Object.values(answer.values).some((value) => value.trim() !== \"\");\n case \"sort_order\":\n // The view stores a sort answer once the customer moved an item.\n return answer.orderedIds.length > 0;\n }\n}\n\nexport function countUnanswered(\n questions: TestQuestion[],\n answers: TestAnswers,\n): number {\n return questions.filter((q) => !isAnswered(q, answers[q.id])).length;\n}\n\n/** Moves the element at `from` to `to`; out of range moves change nothing. */\nexport function moveItem<T>(list: readonly T[], from: number, to: number): T[] {\n if (\n from === to ||\n from < 0 ||\n to < 0 ||\n from >= list.length ||\n to >= list.length\n ) {\n return [...list];\n }\n const copy = [...list];\n const [item] = copy.splice(from, 1);\n copy.splice(to, 0, item!);\n return copy;\n}\n\nexport type GapSegment =\n | { type: \"text\"; text: string }\n | { type: \"blank\"; id: string };\n\n/**\n * Splits a fill-in-the-gap template (`Paris is in {{g1}}`) into text and\n * blanks. Placeholders that are not in `blanks` stay text.\n */\nexport function splitGapTemplate(\n template: string,\n blanks: readonly string[],\n): GapSegment[] {\n const segments: GapSegment[] = [];\n const pattern = /\\{\\{\\s*([^{}\\s]+)\\s*\\}\\}/g;\n let last = 0;\n for (const match of template.matchAll(pattern)) {\n const id = match[1]!;\n if (!blanks.includes(id)) {\n continue;\n }\n if (match.index > last) {\n segments.push({ type: \"text\", text: template.slice(last, match.index) });\n }\n segments.push({ type: \"blank\", id });\n last = match.index + match[0].length;\n }\n if (last < template.length) {\n segments.push({ type: \"text\", text: template.slice(last) });\n }\n return segments;\n}\n\n/** A readable form of an answer, used on the result screen. */\nexport function describeAnswer(\n question: TestQuestion,\n answer: QuestionAnswer | undefined,\n): string {\n const { config } = question;\n if (answer == null || answer.type !== config.type) {\n return \"\";\n }\n const label = (list: { id: string; label: string }[], id: string) =>\n list.find((entry) => entry.id === id)?.label ?? id;\n\n if (config.type === \"mcq\" && answer.type === \"mcq\") {\n return answer.selectedIds.map((id) => label(config.choices, id)).join(\", \");\n }\n if (config.type === \"todolist\" && answer.type === \"todolist\") {\n return answer.checkedIds.map((id) => label(config.steps, id)).join(\", \");\n }\n if (config.type === \"sort_order\" && answer.type === \"sort_order\") {\n return answer.orderedIds.map((id) => label(config.items, id)).join(\" → \");\n }\n if (config.type === \"fill_gap\" && answer.type === \"fill_gap\") {\n return splitGapTemplate(config.template, config.blanks)\n .map((segment) =>\n segment.type === \"text\"\n ? segment.text\n : `[${answer.values[segment.id] ?? \"\"}]`,\n )\n .join(\"\");\n }\n return \"\";\n}\n"]}
|
package/dist/course.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{b as at,c as it}from"./chunk-5X7TP3TQ.mjs";import{b as rt}from"./chunk-CUPG4B5W.mjs";import{n as st,o as nt,q as T,r as ot,s as we,u as fe,v as Ie}from"./chunk-EVIDNS22.mjs";import{a as y}from"./chunk-5R6AGO3Z.mjs";import"./chunk-GEBRXFT3.mjs";import{l as He,n as qe,r as Oe,s as $e}from"./chunk-CF45RDMD.mjs";import{b as et,f as tt}from"./chunk-VNYHJT6V.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";import{ChevronLeft as Ke,List as qs,Play as Os}from"lucide-react";import{useCallback as Jt,useEffect as je,useId as $s,useMemo as Fe,useRef as en,useState as Te}from"react";function lt(e){let t={},s=e?.brand?.trim(),o=e?.brandDark?.trim()||s;if(s){t["--course-accent"]=s;let n=we(s);n&&(t["--course-accent-fg"]=n)}if(o){t["--course-accent-dark"]=o;let n=we(o);n&&(t["--course-accent-dark-fg"]=n)}return t}import{useCallback as Ot}from"react";import{createContext as Ht,useContext as qt}from"react";var ct=Ht(void 0),ut=ct.Provider;function C(){let e=qt(ct);if(e==null)throw new Error("Course components have to be rendered inside PanelCourse");return e}function F(){let{analytics:e,preview:t}=C();return Ot((s,o)=>{t||e?.courseId==null||e.instrument.track(s,{...o,course_id:e.courseId})},[e,t])}import{CheckCircle2 as ts,ClipboardCheck as ss,Lock as ns,PlayCircle as os,FileText as rs}from"lucide-react";import{useEffect as ht,useId as as,useRef as is,useState as ls}from"react";function xe(e){return[...e.sections.flatMap(t=>t.items.map(s=>({...s,sectionId:t.id}))),...e.finalItems.map(t=>({...t,sectionId:""}))]}function le(e,t){if(!(t==null||t===""))return xe(e).find(s=>s.id===t)}function Ee(e,t){let s=xe(e),o=s.findIndex(n=>n.id===t);return o<0?{}:{prev:s[o-1],next:s[o+1]}}function dt(e,t){let{next:s}=Ee(e,t);return s!=null&&s.kind==="lesson"&&!s.locked?s:void 0}function $t(e){let t=0,s=0;for(let o of e.sections)for(let n of o.items)n.kind==="lesson"&&(t+=1,n.completed&&(s+=1));return{total:t,completed:s,percent:t===0?0:Math.round(s/t*100)}}function pt(e,t){let s=e.sections.map(n=>({...n,items:n.items.map(a=>a.id===t&&a.kind==="lesson"?{...a,completed:!0}:a)})),o=$t({sections:s});return{...e,sections:s,totalLessons:o.total,completedLessons:o.completed,progressPercent:o.percent}}function mt(e,t){return le(e,t)!=null?t:le(e,e.resumeItemId)?.locked===!1?e.resumeItemId:xe(e).find(s=>!s.locked)?.id}function I(e,t){return e.replace(/\{(\w+)\}/g,(s,o)=>o in t?String(t[o]):s)}function gt(e,t){return t>0&&e/t>=.8}function bt(e,t){return e<5||t>0&&e>=t-5?0:Math.floor(e)}function Ge(e){let t=Math.max(0,Math.floor(e)),s=Math.floor(t/3600),o=Math.floor(t%3600/60),n=String(t%60).padStart(2,"0");return s>0?`${s}:${String(o).padStart(2,"0")}:${n}`:`${o}:${n}`}function ft(e){if(!(e>0))return"0 B";let t=["B","KB","MB","GB"],s=Math.min(Math.floor(Math.log(e)/Math.log(1024)),t.length-1),o=e/1024**s;return`${s===0||o>=10?Math.round(o):o.toFixed(1)} ${t[s]}`}function xt(e){switch(e.type){case"mcq":return{type:"mcq",selectedIds:[]};case"todolist":return{type:"todolist",checkedIds:[]};case"fill_gap":return{type:"fill_gap",values:{}};case"sort_order":return{type:"sort_order",orderedIds:e.items.map(t=>t.id)}}}function es(e,t){if(t==null||t.type!==e.config.type)return!1;switch(t.type){case"mcq":return t.selectedIds.length>0;case"todolist":return t.checkedIds.length>0;case"fill_gap":return Object.values(t.values).some(s=>s.trim()!=="");case"sort_order":return t.orderedIds.length>0}}function At(e,t){return e.filter(s=>!es(s,t[s.id])).length}function yt(e,t,s){if(t===s||t<0||s<0||t>=e.length||s>=e.length)return[...e];let o=[...e],[n]=o.splice(t,1);return o.splice(s,0,n),o}function Ue(e,t){let s=[],o=/\{\{\s*([^{}\s]+)\s*\}\}/g,n=0;for(let a of e.matchAll(o)){let r=a[1];t.includes(r)&&(a.index>n&&s.push({type:"text",text:e.slice(n,a.index)}),s.push({type:"blank",id:r}),n=a.index+a[0].length)}return n<e.length&&s.push({type:"text",text:e.slice(n)}),s}function St(e,t){let{config:s}=e;if(t==null||t.type!==s.type)return"";let o=(n,a)=>n.find(r=>r.id===a)?.label??a;return s.type==="mcq"&&t.type==="mcq"?t.selectedIds.map(n=>o(s.choices,n)).join(", "):s.type==="todolist"&&t.type==="todolist"?t.checkedIds.map(n=>o(s.steps,n)).join(", "):s.type==="sort_order"&&t.type==="sort_order"?t.orderedIds.map(n=>o(s.items,n)).join(" \u2192 "):s.type==="fill_gap"&&t.type==="fill_gap"?Ue(s.template,s.blanks).map(n=>n.type==="text"?n.text:`[${t.values[n.id]??""}]`).join(""):""}import{jsx as w,jsxs as J}from"react/jsx-runtime";function vt({item:e}){return e.locked?w(ns,{className:"size-4 text-neutral-500","aria-hidden":!0}):e.completed?w(ts,{className:"size-4 text-emerald-600 dark:text-emerald-400","aria-hidden":!0}):e.kind==="test"?w(ss,{className:"size-4 text-neutral-500","aria-hidden":!0}):e.durationSeconds>0?w(os,{className:"size-4 text-neutral-500","aria-hidden":!0}):w(rs,{className:"size-4 text-neutral-500","aria-hidden":!0})}function cs({item:e,selected:t,variant:s,onSelect:o}){let{texts:n}=C(),a=e.lockReason?I(n.lockedByTest,{test:e.lockReason.gatingTestTitle}):n.locked,r=e.locked?a:e.completed?n.completed:void 0;return w("li",{children:J("button",{type:"button","data-item-id":e.id,"data-locked":e.locked||void 0,"aria-current":t?"true":void 0,"aria-disabled":e.locked||void 0,title:e.locked?a:void 0,onClick:()=>{e.locked||o(e)},className:y("flex min-h-11 w-full items-start gap-3 rounded-lg p-2 text-left text-sm outline-none transition focus-visible:ring-[3px] focus-visible:ring-ring/50",t?"bg-neutral-200 dark:bg-neutral-700":"hover:bg-neutral-100 dark:hover:bg-neutral-800",e.locked&&"cursor-not-allowed opacity-60 hover:bg-transparent dark:hover:bg-transparent"),children:[s==="playlist"&&e.kind==="lesson"&&J("span",{className:"relative aspect-video w-24 shrink-0 overflow-hidden rounded bg-neutral-200 dark:bg-neutral-800",children:[e.thumbnailUrl&&w("img",{src:e.thumbnailUrl,alt:"",loading:"lazy",draggable:!1,className:"size-full object-cover"}),e.durationSeconds>0&&w("span",{className:"absolute right-1 bottom-1 rounded bg-black/75 px-1 text-[10px] text-white",children:Ge(e.durationSeconds)})]}),(s==="modules"||e.kind==="test")&&w("span",{className:"mt-0.5 shrink-0",children:w(vt,{item:e})}),J("span",{className:"min-w-0 flex-1",children:[w("span",{className:"block font-medium break-words",children:e.title}),J("span",{className:"mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-neutral-500 dark:text-neutral-400",children:[e.kind==="test"&&w("span",{children:n.testBadge}),e.isFree&&!e.locked&&w("span",{children:n.free}),s==="modules"&&e.durationSeconds>0&&w("span",{children:Ge(e.durationSeconds)}),r&&w("span",{children:r})]})]}),s==="playlist"&&e.kind==="lesson"&&w("span",{className:"mt-0.5 shrink-0",children:w(vt,{item:e})})]})})}function Ct({course:e,selectedId:t,onSelect:s,variant:o,className:n}){let{texts:a}=C(),r=as(),i=is(null),u=e.sections.find(l=>l.items.some(p=>p.id===t))?.id,[b,A]=ls(()=>new Set(u?[u]:[]));ht(()=>{u&&A(l=>new Set([...l,u]))},[u]),ht(()=>{let l=i.current?.querySelector("[aria-current]");l&&!l.closest("[hidden]")&&l.scrollIntoView?.({block:"nearest"})},[t,b]);let V=l=>w("ul",{className:"flex flex-col gap-1",children:l.map(p=>w(cs,{item:p,selected:p.id===t,variant:o,onSelect:s},p.id))});return J("nav",{ref:i,"aria-label":a.contents,className:y("space-y-4",n),children:[e.sections.map(l=>{let p=l.items.filter(d=>d.kind==="lesson"),f=p.filter(d=>d.completed).length;return J("section",{"data-section-id":l.id,children:[w("h3",{className:"mb-1 text-sm font-semibold",children:J("button",{type:"button",className:"flex min-h-11 w-full items-center justify-between gap-2 rounded-lg px-2 text-left focus-visible:outline focus-visible:outline-2","aria-expanded":b.has(l.id),"aria-controls":`${r}-${l.id}`,onClick:()=>A(d=>{let N=new Set(d);return N.has(l.id)?N.delete(l.id):N.add(l.id),N}),children:[w("span",{className:"break-words",children:l.title}),p.length>0&&J("span",{className:"shrink-0 text-xs font-normal text-neutral-500 dark:text-neutral-400",children:[f,"/",p.length]})]})}),w("div",{id:`${r}-${l.id}`,hidden:!b.has(l.id),children:V(l.items)})]},l.id)}),e.finalItems.length>0&&w("section",{"data-section-id":"final",children:V(e.finalItems)})]})}import{createClient as Rs}from"@connectrpc/connect";import{enumDesc as Cn,fileDesc as us,messageDesc as Rn,serviceDesc as ds}from"@bufbuild/protobuf/codegenv2";import{file_google_protobuf_timestamp as ps}from"@bufbuild/protobuf/wkt";var ms=us("CiFwdWJsaWMvdjEvdXNlcl9wYW5lbF9jb3Vyc2UucHJvdG8SDXB1YmxpY19hcGkudjEiRAoPUGFuZWxMb2NrUmVhc29uEhYKDmdhdGluZ190ZXN0X2lkGAEgASgJEhkKEWdhdGluZ190ZXN0X3RpdGxlGAIgASgJIrQCCg9QYW5lbENvdXJzZUl0ZW0SCgoCaWQYASABKAkSMAoEa2luZBgCIAEoDjIiLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VJdGVtS2luZBINCgV0aXRsZRgDIAEoCRIPCgdpc19mcmVlGAQgASgIEg4KBmxvY2tlZBgFIAEoCBI4Cgtsb2NrX3JlYXNvbhgGIAEoCzIeLnB1YmxpY19hcGkudjEuUGFuZWxMb2NrUmVhc29uSACIAQESEQoJY29tcGxldGVkGAcgASgIEhgKEGR1cmF0aW9uX3NlY29uZHMYCCABKAUSGgoNdGh1bWJuYWlsX3VybBgJIAEoCUgBiAEBEg4KBmdhdGluZxgKIAEoCEIOCgxfbG9ja19yZWFzb25CEAoOX3RodW1ibmFpbF91cmwicwoSUGFuZWxDb3Vyc2VTZWN0aW9uEgoKAmlkGAEgASgJEg0KBXRpdGxlGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEi0KBWl0ZW1zGAQgAygLMh4ucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUl0ZW0i3wIKC1BhbmVsQ291cnNlEgoKAmlkGAEgASgJEiwKBHR5cGUYAiABKA4yHi5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlVHlwZRINCgV0aXRsZRgDIAEoCRITCgtkZXNjcmlwdGlvbhgEIAEoCRIWCgljb3Zlcl91cmwYBSABKAlIAIgBARIVCg10b3RhbF9sZXNzb25zGAYgASgFEhkKEWNvbXBsZXRlZF9sZXNzb25zGAcgASgFEhgKEHByb2dyZXNzX3BlcmNlbnQYCCABKAUSMwoIc2VjdGlvbnMYCSADKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlU2VjdGlvbhIzCgtmaW5hbF9pdGVtcxgKIAMoCzIeLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VJdGVtEhYKDnJlc3VtZV9pdGVtX2lkGAsgASgJQgwKCl9jb3Zlcl91cmwiRAoZVXNlclBhbmVsR2V0Q291cnNlUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJIosBChpVc2VyUGFuZWxHZXRDb3Vyc2VSZXNwb25zZRItCgdzdWNjZXNzGAEgASgLMhoucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAQggKBnJlc3VsdCJfCgpQYW5lbFZpZGVvEg8KB2hsc191cmwYASABKAkSFwoKcG9zdGVyX3VybBgCIAEoCUgAiAEBEhgKEGR1cmF0aW9uX3NlY29uZHMYAyABKAVCDQoLX3Bvc3Rlcl91cmwiRgoPUGFuZWxBdHRhY2htZW50EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSDAoEc2l6ZRgDIAEoAxILCgN1cmwYBCABKAkiTAoMUGFuZWxJdGVtUmVmEgoKAmlkGAEgASgJEjAKBGtpbmQYAiABKA4yIi5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlSXRlbUtpbmQigQQKC1BhbmVsTGVzc29uEgoKAmlkGAEgASgJEhIKCnNlY3Rpb25faWQYAiABKAkSDQoFdGl0bGUYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSFAoMY29udGVudF9odG1sGAUgASgJEi0KBXZpZGVvGAYgASgLMhkucHVibGljX2FwaS52MS5QYW5lbFZpZGVvSACIAQESMwoLYXR0YWNobWVudHMYByADKAsyHi5wdWJsaWNfYXBpLnYxLlBhbmVsQXR0YWNobWVudBIuCgRwcmV2GAggASgLMhsucHVibGljX2FwaS52MS5QYW5lbEl0ZW1SZWZIAYgBARIuCgRuZXh0GAkgASgLMhsucHVibGljX2FwaS52MS5QYW5lbEl0ZW1SZWZIAogBARIRCgljb21wbGV0ZWQYCiABKAgSGAoQcG9zaXRpb25fc2Vjb25kcxgLIAEoBRIRCglteV9yYXRpbmcYDCABKAUSFgoOYXZlcmFnZV9yYXRpbmcYDSABKAISFAoMcmF0aW5nX2NvdW50GA4gASgFEjcKDnVybHNfZXhwaXJlX2F0GA8gASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgDiAEBQggKBl92aWRlb0IHCgVfcHJldkIHCgVfbmV4dEIRCg9fdXJsc19leHBpcmVfYXQiVwoZVXNlclBhbmVsR2V0TGVzc29uUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEhEKCWxlc3Nvbl9pZBgDIAEoCSKLAQoaVXNlclBhbmVsR2V0TGVzc29uUmVzcG9uc2USLQoHc3VjY2VzcxgBIAEoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25IABI0CgdmYWlsdXJlGAIgASgOMiEucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUZhaWx1cmVIAEIICgZyZXN1bHQipwEKIlVzZXJQYW5lbFNhdmVMZXNzb25Qcm9ncmVzc1JlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIRCglsZXNzb25faWQYAyABKAkSEQoJY29tcGxldGVkGAQgASgIEhgKEHBvc2l0aW9uX3NlY29uZHMYBSABKAUSGAoQZHVyYXRpb25fc2Vjb25kcxgGIAEoBSKFAgojVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2USTAoHc3VjY2VzcxgBIAEoCzI5LnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaUAoGUmVzdWx0EhEKCWNvbXBsZXRlZBgBIAEoCBIZChFjb21wbGV0ZWRfbGVzc29ucxgCIAEoBRIYChBwcm9ncmVzc19wZXJjZW50GAMgASgFQggKBnJlc3VsdCIoCgtQYW5lbENob2ljZRIKCgJpZBgBIAEoCRINCgVsYWJlbBgCIAEoCSJPCg5QYW5lbE1jcUNvbmZpZxIrCgdjaG9pY2VzGAEgAygLMhoucHVibGljX2FwaS52MS5QYW5lbENob2ljZRIQCghtdWx0aXBsZRgCIAEoCCKCAQoTUGFuZWxUb2RvbGlzdENvbmZpZxI2CgVzdGVwcxgBIAMoCzInLnB1YmxpY19hcGkudjEuUGFuZWxUb2RvbGlzdENvbmZpZy5TdGVwGjMKBFN0ZXASCgoCaWQYASABKAkSDQoFbGFiZWwYAiABKAkSEAoIcmVxdWlyZWQYAyABKAgiNgoSUGFuZWxGaWxsR2FwQ29uZmlnEhAKCHRlbXBsYXRlGAEgASgJEg4KBmJsYW5rcxgCIAMoCSJBChRQYW5lbFNvcnRPcmRlckNvbmZpZxIpCgVpdGVtcxgBIAMoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxDaG9pY2UioQIKEVBhbmVsVGVzdFF1ZXN0aW9uEgoKAmlkGAEgASgJEg4KBnByb21wdBgCIAEoCRIOCgZwb2ludHMYAyABKAUSLAoDbWNxGAogASgLMh0ucHVibGljX2FwaS52MS5QYW5lbE1jcUNvbmZpZ0gAEjYKCHRvZG9saXN0GAsgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRvZG9saXN0Q29uZmlnSAASNQoIZmlsbF9nYXAYDCABKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsRmlsbEdhcENvbmZpZ0gAEjkKCnNvcnRfb3JkZXIYDSABKAsyIy5wdWJsaWNfYXBpLnYxLlBhbmVsU29ydE9yZGVyQ29uZmlnSABCCAoGY29uZmlnIqsCCglQYW5lbFRlc3QSCgoCaWQYASABKAkSDQoFdGl0bGUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSDgoGZ2F0aW5nGAQgASgIEh4KFnBhc3NfdGhyZXNob2xkX3BlcmNlbnQYBSABKAUSGAoQYXR0ZW1wdHNfYWxsb3dlZBgGIAEoBRIaCg1hdHRlbXB0c19sZWZ0GAcgASgFSACIAQESFQoNYXR0ZW1wdF9jb3VudBgIIAEoBRIaChJiZXN0X3Njb3JlX3BlcmNlbnQYCSABKAUSDgoGcGFzc2VkGAogASgIEjMKCXF1ZXN0aW9ucxgLIAMoCzIgLnB1YmxpY19hcGkudjEuUGFuZWxUZXN0UXVlc3Rpb25CEAoOX2F0dGVtcHRzX2xlZnQiUwoXVXNlclBhbmVsR2V0VGVzdFJlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIPCgd0ZXN0X2lkGAMgASgJIocBChhVc2VyUGFuZWxHZXRUZXN0UmVzcG9uc2USKwoHc3VjY2VzcxgBIAEoCzIYLnB1YmxpY19hcGkudjEuUGFuZWxUZXN0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSABCCAoGcmVzdWx0IiYKDlBhbmVsTWNxQW5zd2VyEhQKDHNlbGVjdGVkX2lkcxgBIAMoCSIqChNQYW5lbFRvZG9saXN0QW5zd2VyEhMKC2NoZWNrZWRfaWRzGAEgAygJIoIBChJQYW5lbEZpbGxHYXBBbnN3ZXISPQoGdmFsdWVzGAEgAygLMi0ucHVibGljX2FwaS52MS5QYW5lbEZpbGxHYXBBbnN3ZXIuVmFsdWVzRW50cnkaLQoLVmFsdWVzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASIrChRQYW5lbFNvcnRPcmRlckFuc3dlchITCgtvcmRlcmVkX2lkcxgBIAMoCSKDAgoLUGFuZWxBbnN3ZXISEwoLcXVlc3Rpb25faWQYASABKAkSLAoDbWNxGAogASgLMh0ucHVibGljX2FwaS52MS5QYW5lbE1jcUFuc3dlckgAEjYKCHRvZG9saXN0GAsgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRvZG9saXN0QW5zd2VySAASNQoIZmlsbF9nYXAYDCABKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsRmlsbEdhcEFuc3dlckgAEjkKCnNvcnRfb3JkZXIYDSABKAsyIy5wdWJsaWNfYXBpLnYxLlBhbmVsU29ydE9yZGVyQW5zd2VySABCBwoFdmFsdWUijQEKE1BhbmVsVGVzdFN1Ym1pc3Npb24SCgoCaWQYASABKAkSFQoNc2NvcmVfcGVyY2VudBgCIAEoBRIOCgZwYXNzZWQYAyABKAgSEwoLZHVyYXRpb25fbXMYBCABKAMSLgoKY3JlYXRlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAilwEKE1BhbmVsUXVlc3Rpb25SZXN1bHQSEwoLcXVlc3Rpb25faWQYASABKAkSDwoHY29ycmVjdBgCIAEoCBIOCgZwb2ludHMYAyABKAUSNwoOY29ycmVjdF9hbnN3ZXIYBCABKAsyGi5wdWJsaWNfYXBpLnYxLlBhbmVsQW5zd2VySACIAQFCEQoPX2NvcnJlY3RfYW5zd2VyIpgBChpVc2VyUGFuZWxTdWJtaXRUZXN0UmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEg8KB3Rlc3RfaWQYAyABKAkSKwoHYW5zd2VycxgEIAMoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxBbnN3ZXISEwoLZHVyYXRpb25fbXMYBSABKAMiyQIKG1VzZXJQYW5lbFN1Ym1pdFRlc3RSZXNwb25zZRJECgdzdWNjZXNzGAEgASgLMjEucHVibGljX2FwaS52MS5Vc2VyUGFuZWxTdWJtaXRUZXN0UmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaowEKBlJlc3VsdBI2CgpzdWJtaXNzaW9uGAEgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRlc3RTdWJtaXNzaW9uEjMKB3Jlc3VsdHMYAiADKAsyIi5wdWJsaWNfYXBpLnYxLlBhbmVsUXVlc3Rpb25SZXN1bHQSGgoNYXR0ZW1wdHNfbGVmdBgDIAEoBUgAiAEBQhAKDl9hdHRlbXB0c19sZWZ0QggKBnJlc3VsdCJfCiNVc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEg8KB3Rlc3RfaWQYAyABKAkiqwIKJFVzZXJQYW5lbExpc3RUZXN0U3VibWlzc2lvbnNSZXNwb25zZRJNCgdzdWNjZXNzGAEgASgLMjoucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAadAoGUmVzdWx0EjcKC3N1Ym1pc3Npb25zGAEgAygLMiIucHVibGljX2FwaS52MS5QYW5lbFRlc3RTdWJtaXNzaW9uEhoKEmJlc3Rfc2NvcmVfcGVyY2VudBgCIAEoBRIVCg1hdHRlbXB0X2NvdW50GAMgASgFQggKBnJlc3VsdCJWChNQYW5lbFF1ZXN0aW9uQW5zd2VyEg8KB2NvbnRlbnQYASABKAkSLgoKY3JlYXRlZF9hdBgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAizAEKE1BhbmVsTGVzc29uUXVlc3Rpb24SCgoCaWQYASABKAkSDwoHY29udGVudBgCIAEoCRITCgthdXRob3JfbmFtZRgDIAEoCRIPCgdpc19taW5lGAQgASgIEi4KCmNyZWF0ZWRfYXQYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjcKBmFuc3dlchgGIAEoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxRdWVzdGlvbkFuc3dlckgAiAEBQgkKB19hbnN3ZXIiYQojVXNlclBhbmVsTGlzdExlc3NvblF1ZXN0aW9uc1JlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIRCglsZXNzb25faWQYAyABKAki9gEKJFVzZXJQYW5lbExpc3RMZXNzb25RdWVzdGlvbnNSZXNwb25zZRJNCgdzdWNjZXNzGAEgASgLMjoucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0TGVzc29uUXVlc3Rpb25zUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaPwoGUmVzdWx0EjUKCXF1ZXN0aW9ucxgBIAMoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25RdWVzdGlvbkIICgZyZXN1bHQicAohVXNlclBhbmVsQXNrTGVzc29uUXVlc3Rpb25SZXF1ZXN0EhQKDGFjY2Vzc190b2tlbhgBIAEoCRIRCglhY2Nlc3NfaWQYAiABKAkSEQoJbGVzc29uX2lkGAMgASgJEg8KB2NvbnRlbnQYBCABKAkimwEKIlVzZXJQYW5lbEFza0xlc3NvblF1ZXN0aW9uUmVzcG9uc2USNQoHc3VjY2VzcxgBIAEoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25RdWVzdGlvbkgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAQggKBnJlc3VsdCJ4ChpVc2VyUGFuZWxSYXRlTGVzc29uUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEhEKCWxlc3Nvbl9pZBgDIAEoCRINCgVzY29yZRgEIAEoBRIPCgdjb21tZW50GAUgASgJIu4BChtVc2VyUGFuZWxSYXRlTGVzc29uUmVzcG9uc2USRAoHc3VjY2VzcxgBIAEoCzIxLnB1YmxpY19hcGkudjEuVXNlclBhbmVsUmF0ZUxlc3NvblJlc3BvbnNlLlJlc3VsdEgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAGkkKBlJlc3VsdBIRCglteV9yYXRpbmcYASABKAUSFgoOYXZlcmFnZV9yYXRpbmcYAiABKAISFAoMcmF0aW5nX2NvdW50GAMgASgFQggKBnJlc3VsdCr6AgoSUGFuZWxDb3Vyc2VGYWlsdXJlEiAKHFBBTkVMX0NPVVJTRV9GQUlMVVJFX1VOS05PV04QABIlCiFQQU5FTF9DT1VSU0VfRkFJTFVSRV9VTkFVVEhPUklaRUQQARIpCiVQQU5FTF9DT1VSU0VfRkFJTFVSRV9DT1VSU0VfTk9UX0ZPVU5EEAISJwojUEFORUxfQ09VUlNFX0ZBSUxVUkVfSVRFTV9OT1RfRk9VTkQQAxIkCiBQQU5FTF9DT1VSU0VfRkFJTFVSRV9JVEVNX0xPQ0tFRBAEEikKJVBBTkVMX0NPVVJTRV9GQUlMVVJFX05PX0FUVEVNUFRTX0xFRlQQBRIpCiVQQU5FTF9DT1VSU0VfRkFJTFVSRV9JTlZBTElEX0FSR1VNRU5UEAYSJQohUEFORUxfQ09VUlNFX0ZBSUxVUkVfUkFURV9MSU1JVEVEEAcSJAogUEFORUxfQ09VUlNFX0ZBSUxVUkVfVU5BVkFJTEFCTEUQCCptCg9QYW5lbENvdXJzZVR5cGUSHQoZUEFORUxfQ09VUlNFX1RZUEVfVU5LTk9XThAAEh4KGlBBTkVMX0NPVVJTRV9UWVBFX1NUQU5EQVJEEAESGwoXUEFORUxfQ09VUlNFX1RZUEVfVklERU8QAip9ChNQYW5lbENvdXJzZUl0ZW1LaW5kEiIKHlBBTkVMX0NPVVJTRV9JVEVNX0tJTkRfVU5LTk9XThAAEiEKHVBBTkVMX0NPVVJTRV9JVEVNX0tJTkRfTEVTU09OEAESHwobUEFORUxfQ09VUlNFX0lURU1fS0lORF9URVNUEAIy+QcKFlVzZXJQYW5lbENvdXJzZVNlcnZpY2USYAoJR2V0Q291cnNlEigucHVibGljX2FwaS52MS5Vc2VyUGFuZWxHZXRDb3Vyc2VSZXF1ZXN0GikucHVibGljX2FwaS52MS5Vc2VyUGFuZWxHZXRDb3Vyc2VSZXNwb25zZRJgCglHZXRMZXNzb24SKC5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldExlc3NvblJlcXVlc3QaKS5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldExlc3NvblJlc3BvbnNlEnsKElNhdmVMZXNzb25Qcm9ncmVzcxIxLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVxdWVzdBoyLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2USWgoHR2V0VGVzdBImLnB1YmxpY19hcGkudjEuVXNlclBhbmVsR2V0VGVzdFJlcXVlc3QaJy5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldFRlc3RSZXNwb25zZRJjCgpTdWJtaXRUZXN0EikucHVibGljX2FwaS52MS5Vc2VyUGFuZWxTdWJtaXRUZXN0UmVxdWVzdBoqLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU3VibWl0VGVzdFJlc3BvbnNlEn4KE0xpc3RUZXN0U3VibWlzc2lvbnMSMi5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbExpc3RUZXN0U3VibWlzc2lvbnNSZXF1ZXN0GjMucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVzcG9uc2USfgoTTGlzdExlc3NvblF1ZXN0aW9ucxIyLnB1YmxpY19hcGkudjEuVXNlclBhbmVsTGlzdExlc3NvblF1ZXN0aW9uc1JlcXVlc3QaMy5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbExpc3RMZXNzb25RdWVzdGlvbnNSZXNwb25zZRJ4ChFBc2tMZXNzb25RdWVzdGlvbhIwLnB1YmxpY19hcGkudjEuVXNlclBhbmVsQXNrTGVzc29uUXVlc3Rpb25SZXF1ZXN0GjEucHVibGljX2FwaS52MS5Vc2VyUGFuZWxBc2tMZXNzb25RdWVzdGlvblJlc3BvbnNlEmMKClJhdGVMZXNzb24SKS5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbFJhdGVMZXNzb25SZXF1ZXN0GioucHVibGljX2FwaS52MS5Vc2VyUGFuZWxSYXRlTGVzc29uUmVzcG9uc2ViBnByb3RvMw",[ps]);var Rt=ds(ms,0);import{Code as ce,ConnectError as gs}from"@connectrpc/connect";import{timestampDate as Ne}from"@bufbuild/protobuf/wkt";var B=class extends Error{code;constructor(t,s){super(s??t),this.name="CourseError",this.code=t}};var bs={0:"unknown",1:"unauthorized",2:"course_not_found",3:"item_not_found",4:"item_locked",5:"no_attempts_left",6:"invalid_argument",7:"rate_limited",8:"unavailable"};function fs(e){return bs[e]??"unknown"}function xs(e){if(e instanceof B)return e;let t=gs.from(e);switch(t.code){case ce.Unauthenticated:case ce.PermissionDenied:return new B("unauthorized",t.rawMessage);case ce.NotFound:return new B("course_not_found",t.rawMessage);case ce.ResourceExhausted:return new B("rate_limited",t.rawMessage);case ce.Unavailable:case ce.DeadlineExceeded:return new B("unavailable",t.rawMessage);default:return e}}function K(e){let{result:t}=e;if(t.case==="success")return t.value;throw t.case==="failure"?new B(fs(t.value)):new B("unknown","empty response")}function kt(e){return{id:e.id,kind:e.kind===2?"test":"lesson",title:e.title,isFree:e.isFree,locked:e.locked,lockReason:e.locked&&e.lockReason!=null?{gatingTestId:e.lockReason.gatingTestId,gatingTestTitle:e.lockReason.gatingTestTitle}:void 0,completed:e.completed,durationSeconds:e.durationSeconds,thumbnailUrl:e.thumbnailUrl||void 0,gating:e.gating}}function As(e){return{id:e.id,type:e.type===2?"video":"standard",title:e.title,description:e.description,coverUrl:e.coverUrl||void 0,totalLessons:e.totalLessons,completedLessons:e.completedLessons,progressPercent:e.progressPercent,sections:e.sections.map(t=>({id:t.id,title:t.title,description:t.description,items:t.items.map(kt)})),finalItems:e.finalItems.map(kt),resumeItemId:e.resumeItemId}}function Nt(e){return e==null?void 0:{id:e.id,kind:e.kind===2?"test":"lesson"}}function ys(e){return{id:e.id,sectionId:e.sectionId,title:e.title,description:e.description,contentHtml:e.contentHtml,video:e.video!=null&&e.video.hlsUrl!==""?{hlsUrl:e.video.hlsUrl,posterUrl:e.video.posterUrl||void 0,durationSeconds:e.video.durationSeconds}:void 0,attachments:e.attachments.map(t=>({id:t.id,name:t.name,size:Number(t.size),url:t.url})),prev:Nt(e.prev),next:Nt(e.next),completed:e.completed,positionSeconds:e.positionSeconds,myRating:e.myRating,averageRating:e.averageRating,ratingCount:e.ratingCount,urlsExpireAt:e.urlsExpireAt!=null?Ne(e.urlsExpireAt):void 0}}function Ss(e){let t={id:e.id,prompt:e.prompt,points:e.points},{config:s}=e;switch(s.case){case"mcq":return{...t,config:{type:"mcq",choices:s.value.choices.map(o=>({id:o.id,label:o.label})),multiple:s.value.multiple}};case"todolist":return{...t,config:{type:"todolist",steps:s.value.steps.map(o=>({id:o.id,label:o.label,required:o.required}))}};case"fillGap":return{...t,config:{type:"fill_gap",template:s.value.template,blanks:[...s.value.blanks]}};case"sortOrder":return{...t,config:{type:"sort_order",items:s.value.items.map(o=>({id:o.id,label:o.label}))}};default:return}}function hs(e){return{id:e.id,title:e.title,description:e.description,gating:e.gating,passThresholdPercent:e.passThresholdPercent,attemptsAllowed:e.attemptsAllowed,attemptsLeft:e.attemptsLeft,attemptCount:e.attemptCount,bestScorePercent:e.bestScorePercent,passed:e.passed,questions:e.questions.map(Ss).filter(t=>t!=null)}}function Pt(e){return{id:e.id,scorePercent:e.scorePercent,passed:e.passed,durationMs:Number(e.durationMs),createdAt:e.createdAt!=null?Ne(e.createdAt):void 0}}function vs(e){let{value:t}=e;switch(t.case){case"mcq":return{type:"mcq",selectedIds:[...t.value.selectedIds]};case"todolist":return{type:"todolist",checkedIds:[...t.value.checkedIds]};case"fillGap":return{type:"fill_gap",values:{...t.value.values}};case"sortOrder":return{type:"sort_order",orderedIds:[...t.value.orderedIds]};default:return}}function Cs(e,t){switch(t.type){case"mcq":return{questionId:e,value:{case:"mcq",value:{selectedIds:t.selectedIds}}};case"todolist":return{questionId:e,value:{case:"todolist",value:{checkedIds:t.checkedIds}}};case"fill_gap":return{questionId:e,value:{case:"fillGap",value:{values:t.values}}};case"sort_order":return{questionId:e,value:{case:"sortOrder",value:{orderedIds:t.orderedIds}}}}}function Vt(e){return{id:e.id,content:e.content,authorName:e.authorName,isMine:e.isMine,createdAt:e.createdAt!=null?Ne(e.createdAt):void 0,answer:e.answer!=null?{content:e.answer.content,createdAt:e.answer.createdAt!=null?Ne(e.answer.createdAt):void 0}:void 0}}function Le(e,t){let s=()=>({accessId:t.accessId,accessToken:typeof t.accessToken=="function"?t.accessToken()??"":t.accessToken});async function o(n){try{return await n()}catch(a){throw xs(a)}}return{getCourse:()=>o(async()=>As(K(await e.getCourse(s())))),getLesson:n=>o(async()=>ys(K(await e.getLesson({...s(),lessonId:n})))),saveProgress:n=>o(async()=>{let a=K(await e.saveLessonProgress({...s(),...n}));return{completed:a.completed,completedLessons:a.completedLessons,progressPercent:a.progressPercent}}),getTest:n=>o(async()=>hs(K(await e.getTest({...s(),testId:n})))),submitTest:(n,a,r)=>o(async()=>{let i=K(await e.submitTest({...s(),testId:n,durationMs:BigInt(Math.max(0,Math.round(r))),answers:Object.entries(a).map(([u,b])=>Cs(u,b))}));if(i.submission==null)throw new B("unknown","missing submission");return{submission:Pt(i.submission),results:i.results.map(u=>({questionId:u.questionId,correct:u.correct,points:u.points,correctAnswer:u.correctAnswer!=null?vs(u.correctAnswer):void 0})),attemptsLeft:i.attemptsLeft}}),listSubmissions:n=>o(async()=>{let a=K(await e.listTestSubmissions({...s(),testId:n}));return{submissions:a.submissions.map(Pt),bestScorePercent:a.bestScorePercent,attemptCount:a.attemptCount}}),listQuestions:n=>o(async()=>K(await e.listLessonQuestions({...s(),lessonId:n})).questions.map(Vt)),askQuestion:(n,a)=>o(async()=>Vt(K(await e.askLessonQuestion({...s(),lessonId:n,content:a})))),rate:(n,a,r="")=>o(async()=>{let i=K(await e.rateLesson({...s(),lessonId:n,score:a,comment:r}));return{myRating:i.myRating,averageRating:i.averageRating,ratingCount:i.ratingCount}})}}function Be(e){let t=e.host??st(),s=Rs(Rt,nt(t));return Le(s,{accessId:e.accessId,accessToken:e.accessToken??(()=>{let{projectID:o,host:n}=rt();return it(o,n)})})}import{ChevronLeft as Ls,ChevronRight as Bs,Download as Zs,Paperclip as Xs}from"lucide-react";import{useEffect as zt,useId as Ms,useMemo as zs,useRef as We,useState as ye}from"react";import{useState as ee}from"react";import{Star as ks}from"lucide-react";import{useState as Ns}from"react";import{jsx as Pe}from"react/jsx-runtime";function Tt({value:e,onSelect:t,disabled:s}){let{texts:o}=C(),[n,a]=Ns(0),r=n||e;return Pe("div",{className:"flex items-center gap-1",role:t?"radiogroup":"img","aria-label":I(o.rateStar,{n:Math.round(e)}),children:[1,2,3,4,5].map(i=>{let u=i<=Math.round(r),b=Pe(ks,{className:y("size-5",u?"fill-amber-400 text-amber-400":"text-neutral-300 dark:text-neutral-600"),"aria-hidden":!0});return t?Pe("button",{type:"button",role:"radio","aria-checked":i===e,"aria-label":I(o.rateStar,{n:i}),disabled:s,className:"rounded p-0.5 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50",onMouseEnter:()=>a(i),onMouseLeave:()=>a(0),onClick:()=>t(i),children:b},i):Pe("span",{children:b},i)})})}import{AlertTriangle as Ps}from"lucide-react";import{jsx as D,jsxs as Ze}from"react/jsx-runtime";var wt="bg-[var(--course-accent,var(--primary))] text-[var(--course-accent-fg,var(--primary-foreground))] dark:bg-[var(--course-accent-dark,var(--course-accent,var(--primary)))] dark:text-[var(--course-accent-dark-fg,var(--course-accent-fg,var(--primary-foreground)))]",H=y(wt,"hover:bg-[var(--course-accent,var(--primary))] hover:opacity-90 dark:hover:bg-[var(--course-accent-dark,var(--course-accent,var(--primary)))]"),z="rounded-xl bg-white p-4 drop-shadow-xl sm:p-6 dark:border dark:border-neutral-700 dark:bg-neutral-900 dark:drop-shadow-none";function q(e,t){if(e instanceof B)switch(e.code){case"unauthorized":return t.errorUnauthorized;case"course_not_found":case"item_not_found":return t.errorNotFound;case"unavailable":return t.errorUnavailable}return t.errorGeneric}function oe({error:e,onRetry:t,className:s}){let{texts:o}=C();return Ze("div",{role:"alert",className:y("flex flex-col items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700 dark:border-red-900 dark:bg-red-950 dark:text-red-300",s),children:[Ze("p",{className:"flex items-center gap-2 font-medium",children:[D(Ps,{className:"size-4 shrink-0","aria-hidden":!0}),q(e,o)]}),t&&D(T,{variant:"outline",size:"sm",onClick:t,children:o.retry})]})}function Ae({label:e}){let{texts:t}=C();return Ze("div",{role:"status","aria-label":e??t.loading,className:"space-y-3",children:[D(fe,{className:"aspect-video w-full"}),D(fe,{className:"h-6 w-2/3"}),D(fe,{className:"h-4 w-full"}),D(fe,{className:"h-4 w-5/6"})]})}function It({percent:e,label:t}){let s=Math.max(0,Math.min(100,Math.round(e)));return D("div",{role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":s,"aria-label":t,className:"h-2 w-full overflow-hidden rounded-full bg-neutral-200 dark:bg-neutral-700",children:D("div",{className:y("h-full rounded-full transition-all duration-300",wt),style:{width:`${s}%`}})})}import{useCallback as Xe,useEffect as Vs,useRef as Me,useState as Et}from"react";function O(e,t,s){let[o,n]=Et(s!==void 0?{status:"success",data:s}:{status:"loading"}),[a,r]=Et(0),i=Me(0),u=Me(e);u.current=e;let b=Me(s!==void 0);Vs(()=>{if(b.current){b.current=!1;return}let p=++i.current;return n({status:"loading"}),u.current().then(f=>{p===i.current&&n({status:"success",data:f})}).catch(f=>{p===i.current&&n({status:"error",error:f})}),()=>{i.current+=1}},[...t,a]);let A=Xe(()=>r(p=>p+1),[]),V=Xe(()=>{let p=++i.current;u.current().then(f=>{p===i.current&&n({status:"success",data:f})}).catch(()=>{})},[]),l=Xe(p=>{n(f=>f.status==="success"?{status:"success",data:p(f.data)}:f)},[]);return{...o,reload:A,refresh:V,mutate:l}}import{jsx as G,jsxs as $}from"react/jsx-runtime";function Ut({lessonId:e,initial:t}){let{dataSource:s,texts:o,preview:n}=C(),a=F(),[r,i]=ee(t),[u,b]=ee(t.myRating),[A,V]=ee(""),[l,p]=ee("idle"),[f,d]=ee();async function N(){if(!(u<1)){p("saving");try{i(await s.rate(e,u,A.trim())),p("saved"),a("lesson_rated",{lesson_id:e,rating:u})}catch(v){d(v),p("error")}}}return $("section",{"aria-labelledby":`rate-${e}`,className:"space-y-3",children:[G("h3",{id:`rate-${e}`,className:"text-lg font-semibold",children:o.rateTitle}),G(Tt,{value:u,onSelect:v=>{b(v),p("idle")},disabled:n||l==="saving"}),r.ratingCount>0&&G("p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:I(o.rateAverage,{average:r.averageRating.toFixed(1),count:r.ratingCount})}),!n&&u>0&&$("div",{className:"space-y-2",children:[G(Ie,{value:A,maxLength:1e3,placeholder:o.ratePlaceholder,onChange:v=>{V(v.target.value),p("idle")}}),G(T,{size:"sm",className:H,disabled:l==="saving",onClick:N,children:o.rateSubmit})]}),l==="saved"&&G("p",{role:"status",className:"text-sm text-emerald-700 dark:text-emerald-400",children:o.rateSaved}),l==="error"&&G("p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:q(f,o)})]})}function Gt(e){return e==null?"":e.toLocaleDateString()}function Lt({lessonId:e}){let{dataSource:t,texts:s,preview:o}=C(),n=O(()=>t.listQuestions(e),[t,e]),[a,r]=ee(""),[i,u]=ee("idle"),[b,A]=ee();async function V(){let l=a.trim();if(l!==""){u("sending");try{let p=await t.askQuestion(e,l);n.mutate(f=>[p,...f]),r(""),u("sent")}catch(p){A(p),u("error")}}}return $("section",{"aria-labelledby":`qa-${e}`,className:"space-y-4",children:[G("h3",{id:`qa-${e}`,className:"text-lg font-semibold",children:s.questionsTitle}),$("form",{className:"space-y-2",onSubmit:l=>{l.preventDefault(),V()},children:[G(Ie,{value:a,maxLength:2e3,placeholder:s.questionPlaceholder,"aria-label":s.questionPlaceholder,onChange:l=>{r(l.target.value),u("idle")}}),G(T,{type:"submit",size:"sm",className:H,disabled:i==="sending"||a.trim()==="",children:s.questionSubmit}),i==="sent"&&G("p",{role:"status",className:"text-sm text-emerald-700 dark:text-emerald-400",children:s.questionSent}),i==="error"&&G("p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:q(b,s)})]}),n.status==="loading"&&G("p",{role:"status",className:"text-sm text-neutral-500",children:s.loading}),n.status==="error"&&G(oe,{error:n.error,onRetry:n.reload}),n.status==="success"&&(n.data.length===0?G("p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:s.questionsEmpty}):G("ul",{className:"space-y-3",children:n.data.map(l=>$("li",{className:"rounded-lg bg-neutral-100 p-3 text-sm dark:bg-neutral-800",children:[$("p",{className:"mb-1 text-xs text-neutral-500 dark:text-neutral-400",children:[l.isMine?s.questionMine:l.authorName,l.createdAt&&` \xB7 ${Gt(l.createdAt)}`]}),G("p",{className:"break-words whitespace-pre-wrap",children:l.content}),l.answer&&$("div",{className:"mt-2 border-l-2 border-neutral-400 pl-3 dark:border-neutral-500",children:[$("p",{className:"mb-1 text-xs font-medium text-neutral-500 dark:text-neutral-400",children:[s.questionTeamAnswer,l.answer.createdAt&&` \xB7 ${Gt(l.answer.createdAt)}`]}),G("p",{className:"break-words whitespace-pre-wrap",children:l.answer.content})]})]},l.id))}))]})}import Ts from"dompurify";var ws={USE_PROFILES:{html:!0},FORBID_TAGS:["style","form","input","button","textarea","select","iframe","frame","object","embed","base","meta","link"],FORBID_ATTR:["style","srcdoc","formaction"],ALLOW_DATA_ATTR:!1,RETURN_TRUSTED_TYPE:!1};function Is(e){if(e.isSupported===!1||e.sanitize==null)return()=>"";let t=e.sanitize.bind(e);return e.addHook?.("afterSanitizeAttributes",s=>{s.tagName==="A"&&s.hasAttribute("href")&&(s.setAttribute("target","_blank"),s.setAttribute("rel","noopener noreferrer nofollow")),s.tagName==="IMG"&&(s.setAttribute("loading","lazy"),s.setAttribute("referrerpolicy","no-referrer"))}),s=>s==null||s===""?"":String(t(s,ws))}var Bt;function ze(e){return typeof window>"u"?"":(Bt??=Is(Ts),Bt(e))}import ue from"hls.js";import{useCallback as Es,useEffect as Gs,useRef as Ve,useState as Zt}from"react";import{jsx as Qe,jsxs as Xt}from"react/jsx-runtime";function Us(e){return e.canPlayType("application/vnd.apple.mpegurl")!==""}function Mt({src:e,poster:t,startAt:s=0,durationHint:o=0,autoPlay:n=!1,lessonId:a,onReport:r,onEnded:i,onExpired:u,className:b}){let{texts:A}=C(),V=F(),l=Ve(V);l.current=V;let p=Ve(null),[f,d]=Zt(!1),[N,v]=Zt(0),x=Ve({onReport:r,onEnded:i,onExpired:u,durationHint:o});x.current={onReport:r,onEnded:i,onExpired:u,durationHint:o};let W=Ve(void 0),L=Es((c,h=p.current)=>{if(h==null||x.current.onReport==null)return;let P=Math.floor(h.currentTime);if(P<=0&&c!=="ended")return;let Z=Number.isFinite(h.duration)?h.duration:x.current.durationHint;W.current=P,x.current.onReport({positionSeconds:P,durationSeconds:Math.floor(Z||0),reason:c})},[]);return Gs(()=>{let c=p.current;if(c==null)return;d(!1);let h,P=!1,Z=!1,k,X=()=>{s>0&&(c.currentTime=s),n&&c.play().catch(()=>{})};Us(c)?(c.src=e,c.addEventListener("loadedmetadata",X,{once:!0})):ue.isSupported()?(h=new ue,h.attachMedia(c),h.loadSource(e),h.on(ue.Events.MANIFEST_PARSED,X),h.on(ue.Events.ERROR,(_,M)=>{if(!M.fatal)return;let Ce=M.response?.code;Ce===401||Ce===403?(x.current.onExpired?.(),d(!0)):M.type===ue.ErrorTypes.NETWORK_ERROR&&!P?(P=!0,h?.startLoad()):M.type===ue.ErrorTypes.MEDIA_ERROR&&!Z?(Z=!0,h?.recoverMediaError()):d(!0)})):c.src=e;let te=()=>{clearInterval(k),k=setInterval(()=>L("periodic"),15e3)},he=()=>{clearInterval(k),c.ended||L("pause")},j=new Set,re=c.currentTime,ae=0,de=_=>{if(a!=null)for(let M of $e(_,j))l.current("video_progress",{lesson_id:a,milestone:M})},ie=()=>{let _=c.currentTime,M=Number.isFinite(c.duration)?c.duration:x.current.durationHint;M>0&&!c.seeking&&_>=re&&_-re<=2&&(ae=Math.max(ae,_),de(ae/M*100)),re=_},pe=()=>{clearInterval(k),a!=null&&!j.has(100)&&(j.add(100),l.current("video_progress",{lesson_id:a,milestone:100})),L("ended"),x.current.onEnded?.()},ve=()=>{h==null&&d(!0)};return c.addEventListener("play",te),c.addEventListener("pause",he),c.addEventListener("ended",pe),c.addEventListener("error",ve),c.addEventListener("timeupdate",ie),()=>{clearInterval(k),c.removeEventListener("play",te),c.removeEventListener("pause",he),c.removeEventListener("ended",pe),c.removeEventListener("error",ve),c.removeEventListener("timeupdate",ie),c.removeEventListener("loadedmetadata",X),!c.ended&&c.currentTime>0&&L("leave",c),h?.destroy(),c.removeAttribute("src"),c.load()}},[e,N]),Xt("div",{className:y("relative overflow-hidden rounded-xl bg-black",b),children:[Qe("video",{ref:p,className:"aspect-video w-full",poster:t,controls:!0,playsInline:!0,preload:"metadata",controlsList:"nodownload"}),f&&Xt("div",{role:"alert",className:"absolute inset-0 flex flex-col items-center justify-center gap-3 bg-black/80 p-4 text-center text-sm text-white",children:[Qe("p",{children:A.videoError}),Qe(T,{variant:"secondary",size:"sm",onClick:()=>{x.current.onExpired?.(),v(c=>c+1)},children:A.videoRetry})]})]})}import{jsx as R,jsxs as Q}from"react/jsx-runtime";function Qt({lesson:e,mobileOutline:t,nextItem:s,position:o=0,courseType:n,prev:a,next:r,autoplay:i,onAutoplayChange:u,startPlaying:b,onNavigate:A,onProgressSaved:V,onEnded:l,onUrlsExpired:p}){let{dataSource:f,texts:d,preview:N}=C(),v=F(),[x,W]=ye(e.completed),L=We(e.completed),c=We(void 0),h=We(e.positionSeconds),[P,Z]=ye(!1),[k,X]=ye(!1),[te,he]=ye(!1),j=Ms(),[re,ae]=ye(n==="video"?"lessons":"description");zt(()=>{v("lesson_started",{lesson_id:e.id,position:o})},[e.id]);let de=e.urlsExpireAt?.getTime();zt(()=>{if(de==null)return;let m=de-Date.now()-6e4;if(m<=0||m>2**31-1)return;let ne=setTimeout(p,m);return()=>clearTimeout(ne)},[de]);let ie=zs(()=>ze(e.contentHtml),[e.contentHtml]);async function pe(m,ne,Y){if(N)return;let be=await f.saveProgress({lessonId:e.id,completed:Y,positionSeconds:m,durationSeconds:ne});c.current=m,be.completed&&!L.current&&(L.current=!0,W(!0),he(!0),v("lesson_completed",{lesson_id:e.id,position:o})),V(e.id,be)}function ve(m){if(h.current=m.positionSeconds,N||m.reason==="periodic"&&m.positionSeconds===c.current)return;let ne=m.reason==="ended"||gt(m.positionSeconds,m.durationSeconds);pe(m.positionSeconds,m.durationSeconds,ne&&!L.current).catch(Y=>{console.warn("Saving the progress of the lesson failed",Y)})}async function _(){Z(!0);try{await pe(h.current,e.video?.durationSeconds??0,!0)}catch(m){console.warn("Marking the lesson as completed failed",m)}finally{Z(!1)}}let M=e.description.trim(),Ce=ie!==""||M!=="",se=[...n==="video"&&t?[{id:"lessons",label:d.lessonsTab??d.contents}]:[],{id:"description",label:d.descriptionTab??e.title},...e.attachments.length?[{id:"files",label:d.filesTab??d.attachments}]:[],{id:"questions",label:d.questionsTab??d.questionsTitle}],me=se.some(m=>m.id===re)?re:se[0].id,Re=m=>y(me!==m&&"hidden","lg:block"),ke=m=>({id:`${j}-panel-${m}`,role:"tabpanel","aria-labelledby":`${j}-tab-${m}`,tabIndex:0}),ge=s?.locked??!1,Dt=!N&&R(T,{size:"sm",variant:x?"outline":"default",className:y("min-h-11 h-auto whitespace-normal",!x&&H),disabled:x||P,onClick:_,children:x?d.markedComplete:d.markComplete}),Je=m=>Q("nav",{"aria-label":d.lessonNavigation??d.contents,"data-lesson-navigation":m?"mobile":"desktop",className:m?"fixed inset-x-0 bottom-[var(--course-mobile-bottom,0px)] z-40 flex items-center justify-between gap-2 border-t border-neutral-200 bg-white px-3 pt-2 pb-[max(0.5rem,env(safe-area-inset-bottom))] dark:border-neutral-700 dark:bg-neutral-900 lg:hidden":"hidden items-center justify-between gap-3 lg:flex",children:[Q(T,{variant:"outline",size:"sm",className:"min-h-11",disabled:a==null,onClick:()=>a&&A(a),children:[R(Ls,{"aria-hidden":!0}),m?d.mobilePrev??d.prevItem:d.prevItem]}),Dt,Q(T,{variant:"outline",size:"sm",className:"min-h-11",disabled:r==null||ge,onClick:()=>r&&!ge&&A(r),children:[m?d.mobileNext??d.nextItem:d.nextItem,R(Bs,{"aria-hidden":!0})]})]});return Q("article",{"data-lesson-id":e.id,className:"min-w-0 space-y-5 pb-[calc(5rem+env(safe-area-inset-bottom)+var(--course-mobile-bottom,0px))] lg:pb-0","aria-labelledby":`lesson-${e.id}`,children:[e.video&&R(Mt,{src:e.video.hlsUrl,poster:e.video.posterUrl,lessonId:e.id,startAt:bt(h.current,e.video.durationSeconds),durationHint:e.video.durationSeconds,autoPlay:b,onReport:ve,onEnded:()=>{X(!0),l()},onExpired:p}),Je(!1),R("div",{className:"lg:hidden",role:"tablist","aria-label":d.contents,children:R("div",{className:"flex overflow-x-auto border-b border-neutral-200 dark:border-neutral-700",children:se.map((m,ne)=>R("button",{type:"button",role:"tab",id:`${j}-tab-${m.id}`,"aria-selected":me===m.id,"aria-controls":`${j}-panel-${m.id}`,tabIndex:me===m.id?0:-1,className:y("min-h-11 min-w-11 shrink-0 border-b-2 px-4 text-sm font-medium focus-visible:outline focus-visible:outline-2",me===m.id?"border-current":"border-transparent text-neutral-600 dark:text-neutral-300"),onClick:()=>ae(m.id),onKeyDown:Y=>{let be=Y.key==="ArrowRight"?1:Y.key==="ArrowLeft"?-1:0;if(!be&&Y.key!=="Home"&&Y.key!=="End")return;Y.preventDefault();let De=Y.key==="Home"?0:Y.key==="End"?se.length-1:(ne+be+se.length)%se.length;ae(se[De].id),Y.currentTarget.parentElement?.querySelectorAll('[role="tab"]')[De]?.focus()},children:m.label},m.id))})}),n==="video"&&t&&R("div",{...ke("lessons"),className:y(z,me!=="lessons"&&"hidden","lg:hidden"),children:t}),Q("div",{className:y(z,"space-y-5"),children:[R("header",{className:"flex flex-wrap items-start justify-between gap-3",children:R("h2",{id:`lesson-${e.id}`,className:"text-2xl font-semibold break-words",children:e.title})}),n==="video"&&e.video&&Q("label",{className:"flex min-h-11 w-fit cursor-pointer items-center gap-2 text-sm text-neutral-600 dark:text-neutral-300",children:[R("input",{type:"checkbox",checked:i,onChange:m=>u(m.target.checked)}),d.autoplay]}),Q("div",{...ke("description"),className:Re("description"),children:[M!==""&&R("p",{className:"text-sm break-words whitespace-pre-wrap text-neutral-700 dark:text-neutral-300",children:M}),ie!==""&&R("div",{className:"bitsnap-course-html break-words [&_a]:underline [&_blockquote]:border-l-4 [&_blockquote]:pl-4 [&_h1]:text-2xl [&_h1]:font-semibold [&_h2]:text-xl [&_h2]:font-semibold [&_h3]:text-lg [&_h3]:font-semibold [&_img]:h-auto [&_img]:max-w-full [&_ol]:list-decimal [&_ol]:pl-6 [&_p]:my-3 [&_pre]:overflow-x-auto [&_table]:block [&_table]:max-w-full [&_table]:overflow-x-auto [&_ul]:list-disc [&_ul]:pl-6",dangerouslySetInnerHTML:{__html:ie}}),!Ce&&!e.video&&R("p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:d.noContent})]}),e.attachments.length>0&&R("div",{...ke("files"),className:Re("files"),children:e.attachments.length>0&&Q("section",{"aria-labelledby":`files-${e.id}`,className:"space-y-2",children:[Q("h3",{id:`files-${e.id}`,className:"flex items-center gap-2 text-lg font-semibold",children:[R(Xs,{className:"size-4","aria-hidden":!0}),d.attachments]}),R("ul",{className:"space-y-2",children:e.attachments.map(m=>R("li",{children:Q("a",{href:m.url,target:"_blank",rel:"noopener noreferrer",download:m.name,className:"flex min-h-11 items-center justify-between gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm transition hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-neutral-700",children:[Q("span",{className:"min-w-0 break-words",children:[m.name,R("span",{className:"ml-2 text-xs text-neutral-500 dark:text-neutral-400",children:ft(m.size)})]}),Q("span",{className:"flex shrink-0 items-center gap-1 text-xs font-medium",children:[d.download,R(Zs,{className:"size-3.5","aria-hidden":!0})]})]})},m.id))})]})})]}),R("div",{className:y(z,Re("description")),children:R(Ut,{lessonId:e.id,initial:{myRating:e.myRating,averageRating:e.averageRating,ratingCount:e.ratingCount}})}),R("div",{...ke("questions"),className:y(z,Re("questions")),children:R(Lt,{lessonId:e.id})}),R("p",{role:"status","aria-live":"polite",className:"text-sm text-emerald-700 dark:text-emerald-400",children:te?d.markedComplete:""}),(x||k)&&r&&s&&Q("section",{"data-course-up-next":!0,className:y(z,"space-y-3"),children:[R("h3",{className:"font-semibold break-words",children:I(d.upNext??`${d.nextItem}: {title}`,{title:s.title})}),ge&&R("p",{className:"text-sm text-neutral-600 dark:text-neutral-300",children:s.lockReason?I(d.lockedByTest,{test:s.lockReason.gatingTestTitle}):d.locked}),R(T,{className:y(H,"min-h-11"),disabled:ge,onClick:()=>!ge&&A(r),children:d.openNext??d.nextItem})]}),Je(!0)]})}var Wt={pl:{back:"Wr\xF3\u0107",loading:"\u0141adowanie kursu...",retry:"Spr\xF3buj ponownie",errorGeneric:"Nie uda\u0142o si\u0119 za\u0142adowa\u0107 danych. Spr\xF3buj ponownie za chwil\u0119.",errorUnauthorized:"Nie masz dost\u0119pu do tego kursu. Zaloguj si\u0119 ponownie lub skontaktuj si\u0119 z pomoc\u0105.",errorNotFound:"Nie znaleziono tego kursu lub lekcji.",errorUnavailable:"Kurs jest chwilowo niedost\u0119pny. Spr\xF3buj ponownie za chwil\u0119.",previewBadge:"Podgl\u0105d",progressLessons:"{done} z {total} lekcji",progressPercent:"{percent}% uko\u0144czono",contents:"Spis tre\u015Bci",free:"Darmowa",locked:"Zablokowane",lockedByTest:"Odblokuje je zaliczenie testu: {test}",lockedPage:"Ta lekcja jest zablokowana. Zalicz test \u201E{test}\u201D, aby j\u0105 odblokowa\u0107.",testBadge:"Test",completed:"Uko\u0144czono",continueCourse:"Kontynuuj kurs",noItem:"Wybierz lekcj\u0119 z listy.",lessonsTab:"Lekcje",descriptionTab:"Opis",filesTab:"Pliki",questionsTab:"Pytania",upNext:"Nast\u0119pna lekcja: {title}",openNext:"Przejd\u017A dalej",courseCompleted:"Kurs uko\u0144czony",lessonNavigation:"Nawigacja lekcji",mobilePrev:"Wstecz",mobileNext:"Dalej",markComplete:"Oznacz jako uko\u0144czon\u0105",markedComplete:"Lekcja uko\u0144czona",prevItem:"Poprzednia",nextItem:"Nast\u0119pna",attachments:"Materia\u0142y do pobrania",download:"Pobierz",autoplay:"Autoodtwarzanie nast\u0119pnej lekcji",videoError:"Nie uda\u0142o si\u0119 odtworzy\u0107 wideo.",videoRetry:"Od\u015Bwie\u017C wideo",noContent:"Ta lekcja nie ma jeszcze tre\u015Bci.",testGating:"Zaliczenie tego testu odblokowuje kolejne lekcje.",testAttemptsLeft:"Pozosta\u0142o pr\xF3b: {count}",testAttemptsUnlimited:"Liczba pr\xF3b nie jest ograniczona.",testNoAttemptsLeft:"Wykorzystano wszystkie pr\xF3by.",testPassThreshold:"Pr\xF3g zaliczenia: {percent}%",testBestScore:"Najlepszy wynik: {percent}%",testSubmit:"Sprawd\u017A odpowiedzi",testSubmitting:"Sprawdzanie...",testUnanswered:"Pytania bez odpowiedzi: {count}",testPassed:"Test zaliczony",testFailed:"Test niezaliczony",testScore:"Wynik: {percent}%",testCorrect:"Poprawnie",testIncorrect:"Niepoprawnie",testCorrectAnswer:"Poprawna odpowied\u017A",testTryAgain:"Spr\xF3buj ponownie",testPreviousAttempts:"Poprzednie podej\u015Bcia",testAttemptRow:"{percent}% \xB7 {date}",testNoQuestions:"Ten test nie ma jeszcze pyta\u0144.",mcqSingle:"Wybierz jedn\u0105 odpowied\u017A",mcqMultiple:"Zaznacz wszystkie poprawne odpowiedzi",todoHint:"Zaznacz wykonane kroki",todoRequired:"wymagany",gapPlaceholder:"uzupe\u0142nij",sortHint:"U\u0142\xF3\u017C elementy w poprawnej kolejno\u015Bci",moveUp:"Przesu\u0144 w g\xF3r\u0119",moveDown:"Przesu\u0144 w d\xF3\u0142",questionsTitle:"Pytania do lekcji",questionsEmpty:"Nikt jeszcze nie zada\u0142 pytania.",questionPlaceholder:"Zadaj pytanie autorowi kursu",questionSubmit:"Wy\u015Blij pytanie",questionSent:"Pytanie wys\u0142ane.",questionMine:"Ty",questionTeamAnswer:"Odpowied\u017A autora",rateTitle:"Oce\u0144 lekcj\u0119",rateStar:"{n} z 5",ratePlaceholder:"Komentarz (opcjonalnie)",rateSubmit:"Zapisz ocen\u0119",rateSaved:"Dzi\u0119kujemy za ocen\u0119.",rateAverage:"\u015Arednia ocena {average} ({count})"},en:{back:"Back",loading:"Loading the course...",retry:"Try again",errorGeneric:"Could not load the data. Please try again in a moment.",errorUnauthorized:"You do not have access to this course. Sign in again or contact support.",errorNotFound:"This course or lesson was not found.",errorUnavailable:"The course is temporarily unavailable. Please try again in a moment.",previewBadge:"Preview",progressLessons:"{done} of {total} lessons",progressPercent:"{percent}% complete",contents:"Contents",free:"Free",locked:"Locked",lockedByTest:"Pass the test to unlock it: {test}",lockedPage:"This lesson is locked. Pass the test \u201C{test}\u201D to unlock it.",testBadge:"Test",completed:"Completed",continueCourse:"Continue the course",noItem:"Pick a lesson from the list.",lessonsTab:"Lessons",descriptionTab:"Description",filesTab:"Files",questionsTab:"Questions",upNext:"Next lesson: {title}",openNext:"Continue",courseCompleted:"Course completed",lessonNavigation:"Lesson navigation",mobilePrev:"Previous",mobileNext:"Next",markComplete:"Mark as completed",markedComplete:"Lesson completed",prevItem:"Previous",nextItem:"Next",attachments:"Downloads",download:"Download",autoplay:"Autoplay the next lesson",videoError:"The video could not be played.",videoRetry:"Reload the video",noContent:"This lesson has no content yet.",testGating:"Passing this test unlocks the following lessons.",testAttemptsLeft:"Attempts left: {count}",testAttemptsUnlimited:"The number of attempts is unlimited.",testNoAttemptsLeft:"You have used all attempts.",testPassThreshold:"Passing score: {percent}%",testBestScore:"Best score: {percent}%",testSubmit:"Check answers",testSubmitting:"Checking...",testUnanswered:"Unanswered questions: {count}",testPassed:"Test passed",testFailed:"Test not passed",testScore:"Score: {percent}%",testCorrect:"Correct",testIncorrect:"Incorrect",testCorrectAnswer:"Correct answer",testTryAgain:"Try again",testPreviousAttempts:"Previous attempts",testAttemptRow:"{percent}% \xB7 {date}",testNoQuestions:"This test has no questions yet.",mcqSingle:"Choose one answer",mcqMultiple:"Select all correct answers",todoHint:"Check the steps you have done",todoRequired:"required",gapPlaceholder:"fill in",sortHint:"Put the items in the correct order",moveUp:"Move up",moveDown:"Move down",questionsTitle:"Questions about this lesson",questionsEmpty:"Nobody has asked a question yet.",questionPlaceholder:"Ask the course author a question",questionSubmit:"Send question",questionSent:"Question sent.",questionMine:"You",questionTeamAnswer:"Answer from the author",rateTitle:"Rate this lesson",rateStar:"{n} of 5",ratePlaceholder:"Comment (optional)",rateSubmit:"Save rating",rateSaved:"Thanks for your rating.",rateAverage:"Average rating {average} ({count})"}};function Ye(e,t=tt()){return et(Wt,t,e)}import{ArrowDown as Qs,ArrowUp as Ws,Check as Ft,X as _t}from"lucide-react";import{useRef as Yt,useState as Se}from"react";import{Fragment as Hs,jsx as g,jsxs as E}from"react/jsx-runtime";function Ys({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=C();if(e.config.type!=="mcq")return null;let{choices:a,multiple:r}=e.config;return E("fieldset",{className:"space-y-2",disabled:s,children:[g("legend",{className:"mb-2 text-xs text-neutral-500 dark:text-neutral-400",children:r?n.mcqMultiple:n.mcqSingle}),a.map(i=>{let u=t.selectedIds.includes(i.id);return E("label",{className:"flex cursor-pointer items-start gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[g("input",{className:"mt-0.5",type:r?"checkbox":"radio",name:`q-${e.id}`,checked:u,onChange:()=>o({type:"mcq",selectedIds:r?u?t.selectedIds.filter(b=>b!==i.id):[...t.selectedIds,i.id]:[i.id]})}),g("span",{className:"break-words",children:i.label})]},i.id)})]})}function Fs({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=C();return e.config.type!=="todolist"?null:E("fieldset",{className:"space-y-2",disabled:s,children:[g("legend",{className:"mb-2 text-xs text-neutral-500 dark:text-neutral-400",children:n.todoHint}),e.config.steps.map(a=>{let r=t.checkedIds.includes(a.id);return E("label",{className:"flex cursor-pointer items-start gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[g("input",{className:"mt-0.5",type:"checkbox",checked:r,onChange:()=>o({type:"todolist",checkedIds:r?t.checkedIds.filter(i=>i!==a.id):[...t.checkedIds,a.id]})}),E("span",{className:"break-words",children:[a.label,a.required&&E("span",{className:"ml-2 text-xs text-neutral-500 dark:text-neutral-400",children:["(",n.todoRequired,")"]})]})]},a.id)})]})}function _s({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=C();if(e.config.type!=="fill_gap")return null;let a=Ue(e.config.template,e.config.blanks);return g("p",{className:"text-sm leading-10 break-words",children:a.map((r,i)=>r.type==="text"?g("span",{className:"whitespace-pre-wrap",children:r.text},i):g(ot,{className:"mx-1 inline-block h-8 w-36 align-middle","aria-label":`${n.gapPlaceholder} ${r.id}`,placeholder:n.gapPlaceholder,disabled:s,value:t.values[r.id]??"",onChange:u=>o({type:"fill_gap",values:{...t.values,[r.id]:u.target.value}})},i))})}function Ks({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=C();if(e.config.type!=="sort_order")return null;let a=new Map(e.config.items.map(i=>[i.id,i.label])),r=(i,u)=>o({type:"sort_order",orderedIds:yt(t.orderedIds,i,u)});return E("div",{className:"space-y-2",children:[g("p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:n.sortHint}),g("ol",{className:"space-y-2",children:t.orderedIds.map((i,u)=>E("li",{className:"flex items-center gap-2 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[E("span",{className:"w-5 text-xs text-neutral-500",children:[u+1,"."]}),g("span",{className:"min-w-0 flex-1 break-words",children:a.get(i)??i}),g(T,{type:"button",variant:"ghost",size:"icon-xs","aria-label":n.moveUp,disabled:s||u===0,onClick:()=>r(u,u-1),children:g(Ws,{"aria-hidden":!0})}),g(T,{type:"button",variant:"ghost",size:"icon-xs","aria-label":n.moveDown,disabled:s||u===t.orderedIds.length-1,onClick:()=>r(u,u+1),children:g(Qs,{"aria-hidden":!0})})]},i))})]})}function js({question:e,answer:t,disabled:s,onChange:o}){let n={question:e,disabled:s,onChange:o};switch(t.type){case"mcq":return g(Ys,{...n,answer:t});case"todolist":return g(Fs,{...n,answer:t});case"fill_gap":return g(_s,{...n,answer:t});case"sort_order":return g(Ks,{...n,answer:t})}}function Js({list:e}){let{texts:t}=C();return e.submissions.length===0?null:E("section",{className:"space-y-2",children:[g("h3",{className:"text-sm font-semibold",children:t.testPreviousAttempts}),g("ul",{className:"space-y-1 text-sm",children:e.submissions.map(s=>E("li",{className:"flex items-center gap-2 text-neutral-700 dark:text-neutral-300",children:[s.passed?g(Ft,{className:"size-4 text-emerald-600","aria-hidden":!0}):g(_t,{className:"size-4 text-red-600","aria-hidden":!0}),I(t.testAttemptRow,{percent:s.scorePercent,date:s.createdAt?.toLocaleString()??""})]},s.id))})]})}function Ds({test:e,onSubmitted:t,onTryAgain:s}){let{dataSource:o,texts:n}=C(),[a,r]=Se({}),[i,u]=Se(),[b,A]=Se(!1),[V,l]=Se(),p=Yt(Date.now()),f=Yt(e.questions).current,d=e.attemptsLeft!=null&&e.attemptsLeft<=0,N=i?i.attemptsLeft:e.attemptsLeft,v=At(f,a),x=c=>a[c.id]??xt(c.config);async function W(){A(!0),l(void 0);try{let c={};for(let P of f)c[P.id]=x(P);let h=await o.submitTest(e.id,c,Date.now()-p.current);u(h),t(h)}catch(c){l(c)}finally{A(!1)}}let L=new Map(i?.results.map(c=>[c.questionId,c]));return f.length===0?g("p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:n.testNoQuestions}):E("form",{className:"space-y-6",onSubmit:c=>{c.preventDefault(),i==null&&!d&&W()},children:[i&&E("div",{role:"status",className:y("rounded-xl p-4 text-sm",i.submission.passed?"bg-emerald-50 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300":"bg-red-50 text-red-800 dark:bg-red-950 dark:text-red-300"),children:[g("p",{className:"text-base font-semibold",children:i.submission.passed?n.testPassed:n.testFailed}),g("p",{children:I(n.testScore,{percent:i.submission.scorePercent})})]}),g("ol",{className:"space-y-6",children:f.map((c,h)=>{let P=L.get(c.id),Z=P?.correctAnswer!=null?St(c,P.correctAnswer):"";return E("li",{className:"space-y-3","data-question-id":c.id,children:[E("p",{className:"font-medium break-words",children:[h+1,". ",c.prompt]}),g(js,{question:c,answer:x(c),disabled:i!=null||b||d,onChange:k=>r(X=>({...X,[c.id]:k}))}),P&&E("div",{className:y("flex items-start gap-2 text-sm",P.correct?"text-emerald-700 dark:text-emerald-400":"text-red-700 dark:text-red-400"),children:[P.correct?g(Ft,{className:"mt-0.5 size-4 shrink-0","aria-hidden":!0}):g(_t,{className:"mt-0.5 size-4 shrink-0","aria-hidden":!0}),E("span",{children:[P.correct?n.testCorrect:n.testIncorrect,Z!==""&&!P.correct&&E("span",{className:"block text-neutral-700 dark:text-neutral-300",children:[n.testCorrectAnswer,": ",Z]})]})]})]},c.id)})}),V!=null&&g("p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:q(V,n)}),i==null?E("div",{className:"space-y-2",children:[v>0&&!d&&g("p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:I(n.testUnanswered,{count:v})}),g(T,{type:"submit",className:H,disabled:b||d,children:b?n.testSubmitting:n.testSubmit})]}):N!==0&&!i.submission.passed&&g(T,{type:"button",variant:"outline",onClick:s,children:n.testTryAgain})]})}function Kt({testId:e,title:t,onSubmitted:s}){let{dataSource:o,texts:n}=C(),a=F(),r=O(()=>o.getTest(e),[o,e]),i=O(()=>o.listSubmissions(e).catch(()=>{}),[o,e]),[u,b]=Se(0);return E("article",{"data-test-id":e,className:y(z,"min-w-0 space-y-5"),children:[g("h2",{className:"text-2xl font-semibold break-words",children:r.status==="success"?r.data.title:t}),r.status==="loading"&&g(Ae,{}),r.status==="error"&&g(oe,{error:r.error,onRetry:r.reload}),r.status==="success"&&E(Hs,{children:[r.data.description.trim()!==""&&g("p",{className:"text-sm break-words whitespace-pre-wrap text-neutral-700 dark:text-neutral-300",children:r.data.description}),E("ul",{className:"space-y-1 text-sm text-neutral-600 dark:text-neutral-400",children:[r.data.gating&&g("li",{children:n.testGating}),g("li",{children:I(n.testPassThreshold,{percent:r.data.passThresholdPercent})}),g("li",{children:r.data.attemptsLeft==null?n.testAttemptsUnlimited:I(n.testAttemptsLeft,{count:r.data.attemptsLeft})}),r.data.attemptCount>0&&g("li",{children:I(n.testBestScore,{percent:r.data.bestScorePercent})})]}),r.data.attemptsLeft!=null&&r.data.attemptsLeft<=0&&g("p",{role:"alert",className:"text-sm font-medium text-red-700 dark:text-red-400",children:n.testNoAttemptsLeft}),g(Ds,{test:r.data,onSubmitted:A=>{a("test_submitted",{lesson_id:e,score_bucket:Oe(A.submission.scorePercent)}),s(A),i.refresh(),r.refresh()},onTryAgain:()=>b(A=>A+1)},u),i.status==="success"&&i.data&&g(Js,{list:i.data})]})]})}import{jsx as S,jsxs as U}from"react/jsx-runtime";var jt="bitsnap-course-autoplay";function tn(){let[e,t]=Te(!1);je(()=>{try{t(window.localStorage.getItem(jt)==="1")}catch{}},[]);let s=Jt(o=>{t(o);try{window.localStorage.setItem(jt,o?"1":"0")}catch{}},[]);return[e,s]}function _e(e){return e==null?void 0:{id:e.id,kind:e.kind}}function sn({item:e}){let{texts:t}=C();return U("div",{className:y(z,"space-y-2"),role:"status",children:[S("h2",{className:"text-2xl font-semibold break-words",children:e.title}),S("p",{className:"text-sm text-neutral-600 dark:text-neutral-300",children:e.lockReason?I(t.lockedPage,{test:e.lockReason.gatingTestTitle}):t.locked})]})}function nn({course:e,item:t,autoplay:s,onAutoplayChange:o,startPlaying:n,onNavigate:a,onProgressSaved:r,onEnded:i,onLocked:u,mobileOutline:b}){let{dataSource:A,texts:V}=C(),l=O(()=>A.getLesson(t.id),[A,t.id]),p=l.status==="error"&&l.error instanceof B&&l.error.code==="item_locked";if(je(()=>{p&&u()},[p]),l.status==="loading")return S(Ae,{});if(l.status==="error")return p?U("div",{className:y(z,"space-y-2"),role:"alert",children:[S("h2",{className:"text-2xl font-semibold break-words",children:t.title}),S("p",{className:"text-sm",children:q(l.error,V)})]}):S(oe,{error:l.error,onRetry:l.reload});let f=Ee(e,t.id),d=xe(e).filter(N=>N.kind!=="test").findIndex(N=>N.id===t.id)+1;return S(Qt,{lesson:l.data,position:d,mobileOutline:b,nextItem:le(e,(l.data.next??_e(f.next))?.id),courseType:e.type,prev:l.data.prev??_e(f.prev),next:l.data.next??_e(f.next),autoplay:s,onAutoplayChange:o,startPlaying:n,onNavigate:a,onProgressSaved:r,onEnded:i,onUrlsExpired:l.refresh},l.data.id)}function on({course:e,selectedId:t,onSelect:s,onBack:o,onCourseChange:n,onCourseRefresh:a}){let{texts:r,preview:i}=C(),u=F(),b=en(null),A=$s(),[V,l]=Te(!1),[p,f]=tn(),[d,N]=Te(),v=mt(e,t),x=le(e,v);je(()=>{u("course_opened",{})},[e.id]);let W=(k,X=!1)=>{N(X?k:void 0),l(!1),s(k),b.current?.scrollIntoView({block:"start",behavior:"smooth"})},L=S(Ct,{course:e,selectedId:v,variant:e.type==="video"?"playlist":"modules",onSelect:k=>W(k.id)}),c;x==null?c=S("p",{className:"text-sm text-neutral-500",children:r.noItem}):x.locked?c=S(sn,{item:x}):x.kind==="test"?c=S(Kt,{testId:x.id,title:x.title,onSubmitted:()=>a()},x.id):c=S(nn,{course:e,item:x,autoplay:p,onAutoplayChange:f,startPlaying:d===x.id,onNavigate:k=>W(k.id),onProgressSaved:(k,X)=>n(te=>({...X.completed?pt(te,k):te,completedLessons:X.completedLessons,progressPercent:X.progressPercent})),onEnded:()=>{if(!p)return;let k=dt(e,x.id);k&&W(k.id,!0)},onLocked:a,mobileOutline:L},x.id);let h=e.type==="video",P=!h||x==null||x.locked||x.kind==="test",Z=e.resumeItemId!==""&&e.resumeItemId!==v&&le(e,e.resumeItemId)?.locked===!1;return U("div",{ref:b,className:"space-y-4 scroll-mt-4",children:[U("header",{className:"space-y-3",children:[U("div",{className:"flex flex-wrap items-center gap-2",children:[o&&U(T,{variant:"ghost",size:"sm",onClick:o,children:[S(Ke,{"aria-hidden":!0}),r.back]}),i&&S("span",{"data-course-preview":!0,className:"rounded-full bg-amber-100 px-3 py-0.5 text-xs font-semibold text-amber-900 dark:bg-amber-900/40 dark:text-amber-200",children:r.previewBadge})]}),U("div",{className:"flex flex-wrap items-end justify-between gap-4",children:[S("div",{className:"min-w-0 space-y-1",children:S("h1",{className:"text-2xl font-semibold break-words sm:text-3xl",children:e.title})}),Z&&U(T,{size:"sm",variant:"outline",onClick:()=>W(e.resumeItemId),children:[S(Os,{"aria-hidden":!0}),r.continueCourse]})]}),U("div",{className:"max-w-xl space-y-1 md:max-w-none",children:[e.progressPercent>=100&&S("p",{role:"status",className:"font-semibold text-emerald-700 dark:text-emerald-400",children:r.courseCompleted??r.completed}),S(It,{percent:e.progressPercent,label:I(r.progressPercent,{percent:e.progressPercent})}),U("p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:[I(r.progressLessons,{done:e.completedLessons,total:e.totalLessons})," \xB7 ",I(r.progressPercent,{percent:e.progressPercent})]})]})]}),U("div",{className:y("grid gap-6",h?"lg:grid-cols-[minmax(0,1fr)_380px]":"lg:grid-cols-[320px_minmax(0,1fr)]"),children:[P&&S("div",{className:"lg:hidden",children:U(T,{variant:"outline",size:"sm",className:"min-h-11","aria-controls":A,"aria-expanded":V,onClick:()=>l(k=>!k),children:[S(qs,{"aria-hidden":!0}),r.contents]})}),P&&S("aside",{id:A,className:y(z,h&&"lg:hidden","p-3 sm:p-3 lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto",V?"block":"hidden",!h&&"lg:block"),children:L}),S("div",{className:"min-w-0","data-course-content":!0,children:c}),h&&S("aside",{className:y(z,"hidden p-3 sm:p-3 lg:block lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto"),children:L})]})]})}function rn({accessId:e,accessToken:t,dataSource:s,preview:o=!1,itemId:n,onItemChange:a,onBack:r,initialCourse:i,texts:u,locale:b,theme:A,className:V,analytics:l}){let p=at(),f=qe(p.projectID,o?!1:l,"course"),d=Fe(()=>Ye(u,b),[u,b]),N=Fe(()=>{if(s)return s;if(e==null||e==="")throw new Error("PanelCourse needs an accessId or a dataSource");return Be({accessId:e,accessToken:t})},[s,e,t]),v=O(()=>N.getCourse(),[N],i),[x,W]=Te(),L=a?n:n??x,c=Jt(k=>{W(k),a?.(k)},[a]),h=lt(A?.colors??p.theme?.colors),P=v.status==="success"?v.data.id:void 0,Z=Fe(()=>({dataSource:N,texts:d,preview:o,analytics:{courseId:P,instrument:f}}),[N,d,o,P,f]);return S(He,{value:f,children:S(ut,{value:Z,children:U("div",{"data-bitsnap-course":!0,"data-course-type":v.status==="success"?v.data.type:void 0,style:h,className:y("font-inter w-full max-w-7xl",V),children:[v.status==="loading"&&U("div",{className:"space-y-4",children:[r&&U(T,{variant:"ghost",size:"sm",onClick:r,children:[S(Ke,{"aria-hidden":!0}),d.back]}),S("p",{className:"sr-only",role:"status",children:d.loading}),S(Ae,{})]}),v.status==="error"&&U("div",{className:"space-y-4",children:[r&&U(T,{variant:"ghost",size:"sm",onClick:r,children:[S(Ke,{"aria-hidden":!0}),d.back]}),S(oe,{error:v.error,onRetry:v.reload})]}),v.status==="success"&&S(on,{course:v.data,selectedId:L,onSelect:c,onBack:r,onCourseChange:v.mutate,onCourseRefresh:v.refresh})]})})})}export{Wt as COURSE_TEXT_TABLES,B as CourseError,rn as PanelCourse,Le as createCourseDataSource,Be as createRpcCourseDataSource,Ye as getCourseTexts,ze as sanitizeLessonHtml};
|
|
1
|
+
import{b as at}from"./chunk-5JRUPLXY.mjs";import{h as st,i as nt,k as T,l as ot,m as we,o as fe,p as Ie}from"./chunk-SCEWYZUZ.mjs";import{a as it}from"./chunk-XEXPQFKN.mjs";import{b as rt}from"./chunk-CUPG4B5W.mjs";import"./chunk-WG54EHST.mjs";import{a as y}from"./chunk-5R6AGO3Z.mjs";import"./chunk-N523LFAM.mjs";import{l as He,n as qe,r as Oe,s as $e}from"./chunk-SSSF64M7.mjs";import"./chunk-MDCP5JNP.mjs";import{b as et,f as tt}from"./chunk-OFPRIENJ.mjs";import"./chunk-U6FLIRIV.mjs";import{ChevronLeft as Ke,List as qs,Play as Os}from"lucide-react";import{useCallback as Jt,useEffect as je,useId as $s,useMemo as Fe,useRef as en,useState as Te}from"react";function lt(e){let t={},s=e?.brand?.trim(),o=e?.brandDark?.trim()||s;if(s){t["--course-accent"]=s;let n=we(s);n&&(t["--course-accent-fg"]=n)}if(o){t["--course-accent-dark"]=o;let n=we(o);n&&(t["--course-accent-dark-fg"]=n)}return t}import{useCallback as Ot}from"react";import{createContext as Ht,useContext as qt}from"react";var ct=Ht(void 0),ut=ct.Provider;function C(){let e=qt(ct);if(e==null)throw new Error("Course components have to be rendered inside PanelCourse");return e}function F(){let{analytics:e,preview:t}=C();return Ot((s,o)=>{t||e?.courseId==null||e.instrument.track(s,{...o,course_id:e.courseId})},[e,t])}import{CheckCircle2 as ts,ClipboardCheck as ss,Lock as ns,PlayCircle as os,FileText as rs}from"lucide-react";import{useEffect as ht,useId as as,useRef as is,useState as ls}from"react";function xe(e){return[...e.sections.flatMap(t=>t.items.map(s=>({...s,sectionId:t.id}))),...e.finalItems.map(t=>({...t,sectionId:""}))]}function le(e,t){if(!(t==null||t===""))return xe(e).find(s=>s.id===t)}function Ee(e,t){let s=xe(e),o=s.findIndex(n=>n.id===t);return o<0?{}:{prev:s[o-1],next:s[o+1]}}function dt(e,t){let{next:s}=Ee(e,t);return s!=null&&s.kind==="lesson"&&!s.locked?s:void 0}function $t(e){let t=0,s=0;for(let o of e.sections)for(let n of o.items)n.kind==="lesson"&&(t+=1,n.completed&&(s+=1));return{total:t,completed:s,percent:t===0?0:Math.round(s/t*100)}}function pt(e,t){let s=e.sections.map(n=>({...n,items:n.items.map(a=>a.id===t&&a.kind==="lesson"?{...a,completed:!0}:a)})),o=$t({sections:s});return{...e,sections:s,totalLessons:o.total,completedLessons:o.completed,progressPercent:o.percent}}function mt(e,t){return le(e,t)!=null?t:le(e,e.resumeItemId)?.locked===!1?e.resumeItemId:xe(e).find(s=>!s.locked)?.id}function I(e,t){return e.replace(/\{(\w+)\}/g,(s,o)=>o in t?String(t[o]):s)}function gt(e,t){return t>0&&e/t>=.8}function bt(e,t){return e<5||t>0&&e>=t-5?0:Math.floor(e)}function Ge(e){let t=Math.max(0,Math.floor(e)),s=Math.floor(t/3600),o=Math.floor(t%3600/60),n=String(t%60).padStart(2,"0");return s>0?`${s}:${String(o).padStart(2,"0")}:${n}`:`${o}:${n}`}function ft(e){if(!(e>0))return"0 B";let t=["B","KB","MB","GB"],s=Math.min(Math.floor(Math.log(e)/Math.log(1024)),t.length-1),o=e/1024**s;return`${s===0||o>=10?Math.round(o):o.toFixed(1)} ${t[s]}`}function xt(e){switch(e.type){case"mcq":return{type:"mcq",selectedIds:[]};case"todolist":return{type:"todolist",checkedIds:[]};case"fill_gap":return{type:"fill_gap",values:{}};case"sort_order":return{type:"sort_order",orderedIds:e.items.map(t=>t.id)}}}function es(e,t){if(t==null||t.type!==e.config.type)return!1;switch(t.type){case"mcq":return t.selectedIds.length>0;case"todolist":return t.checkedIds.length>0;case"fill_gap":return Object.values(t.values).some(s=>s.trim()!=="");case"sort_order":return t.orderedIds.length>0}}function At(e,t){return e.filter(s=>!es(s,t[s.id])).length}function yt(e,t,s){if(t===s||t<0||s<0||t>=e.length||s>=e.length)return[...e];let o=[...e],[n]=o.splice(t,1);return o.splice(s,0,n),o}function Ue(e,t){let s=[],o=/\{\{\s*([^{}\s]+)\s*\}\}/g,n=0;for(let a of e.matchAll(o)){let r=a[1];t.includes(r)&&(a.index>n&&s.push({type:"text",text:e.slice(n,a.index)}),s.push({type:"blank",id:r}),n=a.index+a[0].length)}return n<e.length&&s.push({type:"text",text:e.slice(n)}),s}function St(e,t){let{config:s}=e;if(t==null||t.type!==s.type)return"";let o=(n,a)=>n.find(r=>r.id===a)?.label??a;return s.type==="mcq"&&t.type==="mcq"?t.selectedIds.map(n=>o(s.choices,n)).join(", "):s.type==="todolist"&&t.type==="todolist"?t.checkedIds.map(n=>o(s.steps,n)).join(", "):s.type==="sort_order"&&t.type==="sort_order"?t.orderedIds.map(n=>o(s.items,n)).join(" \u2192 "):s.type==="fill_gap"&&t.type==="fill_gap"?Ue(s.template,s.blanks).map(n=>n.type==="text"?n.text:`[${t.values[n.id]??""}]`).join(""):""}import{jsx as w,jsxs as J}from"react/jsx-runtime";function vt({item:e}){return e.locked?w(ns,{className:"size-4 text-neutral-500","aria-hidden":!0}):e.completed?w(ts,{className:"size-4 text-emerald-600 dark:text-emerald-400","aria-hidden":!0}):e.kind==="test"?w(ss,{className:"size-4 text-neutral-500","aria-hidden":!0}):e.durationSeconds>0?w(os,{className:"size-4 text-neutral-500","aria-hidden":!0}):w(rs,{className:"size-4 text-neutral-500","aria-hidden":!0})}function cs({item:e,selected:t,variant:s,onSelect:o}){let{texts:n}=C(),a=e.lockReason?I(n.lockedByTest,{test:e.lockReason.gatingTestTitle}):n.locked,r=e.locked?a:e.completed?n.completed:void 0;return w("li",{children:J("button",{type:"button","data-item-id":e.id,"data-locked":e.locked||void 0,"aria-current":t?"true":void 0,"aria-disabled":e.locked||void 0,title:e.locked?a:void 0,onClick:()=>{e.locked||o(e)},className:y("flex min-h-11 w-full items-start gap-3 rounded-lg p-2 text-left text-sm outline-none transition focus-visible:ring-[3px] focus-visible:ring-ring/50",t?"bg-neutral-200 dark:bg-neutral-700":"hover:bg-neutral-100 dark:hover:bg-neutral-800",e.locked&&"cursor-not-allowed opacity-60 hover:bg-transparent dark:hover:bg-transparent"),children:[s==="playlist"&&e.kind==="lesson"&&J("span",{className:"relative aspect-video w-24 shrink-0 overflow-hidden rounded bg-neutral-200 dark:bg-neutral-800",children:[e.thumbnailUrl&&w("img",{src:e.thumbnailUrl,alt:"",loading:"lazy",draggable:!1,className:"size-full object-cover"}),e.durationSeconds>0&&w("span",{className:"absolute right-1 bottom-1 rounded bg-black/75 px-1 text-[10px] text-white",children:Ge(e.durationSeconds)})]}),(s==="modules"||e.kind==="test")&&w("span",{className:"mt-0.5 shrink-0",children:w(vt,{item:e})}),J("span",{className:"min-w-0 flex-1",children:[w("span",{className:"block font-medium break-words",children:e.title}),J("span",{className:"mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-neutral-500 dark:text-neutral-400",children:[e.kind==="test"&&w("span",{children:n.testBadge}),e.isFree&&!e.locked&&w("span",{children:n.free}),s==="modules"&&e.durationSeconds>0&&w("span",{children:Ge(e.durationSeconds)}),r&&w("span",{children:r})]})]}),s==="playlist"&&e.kind==="lesson"&&w("span",{className:"mt-0.5 shrink-0",children:w(vt,{item:e})})]})})}function Ct({course:e,selectedId:t,onSelect:s,variant:o,className:n}){let{texts:a}=C(),r=as(),i=is(null),u=e.sections.find(l=>l.items.some(p=>p.id===t))?.id,[b,A]=ls(()=>new Set(u?[u]:[]));ht(()=>{u&&A(l=>new Set([...l,u]))},[u]),ht(()=>{let l=i.current?.querySelector("[aria-current]");l&&!l.closest("[hidden]")&&l.scrollIntoView?.({block:"nearest"})},[t,b]);let V=l=>w("ul",{className:"flex flex-col gap-1",children:l.map(p=>w(cs,{item:p,selected:p.id===t,variant:o,onSelect:s},p.id))});return J("nav",{ref:i,"aria-label":a.contents,className:y("space-y-4",n),children:[e.sections.map(l=>{let p=l.items.filter(d=>d.kind==="lesson"),f=p.filter(d=>d.completed).length;return J("section",{"data-section-id":l.id,children:[w("h3",{className:"mb-1 text-sm font-semibold",children:J("button",{type:"button",className:"flex min-h-11 w-full items-center justify-between gap-2 rounded-lg px-2 text-left focus-visible:outline focus-visible:outline-2","aria-expanded":b.has(l.id),"aria-controls":`${r}-${l.id}`,onClick:()=>A(d=>{let N=new Set(d);return N.has(l.id)?N.delete(l.id):N.add(l.id),N}),children:[w("span",{className:"break-words",children:l.title}),p.length>0&&J("span",{className:"shrink-0 text-xs font-normal text-neutral-500 dark:text-neutral-400",children:[f,"/",p.length]})]})}),w("div",{id:`${r}-${l.id}`,hidden:!b.has(l.id),children:V(l.items)})]},l.id)}),e.finalItems.length>0&&w("section",{"data-section-id":"final",children:V(e.finalItems)})]})}import{createClient as Rs}from"@connectrpc/connect";import{enumDesc as Cn,fileDesc as us,messageDesc as Rn,serviceDesc as ds}from"@bufbuild/protobuf/codegenv2";import{file_google_protobuf_timestamp as ps}from"@bufbuild/protobuf/wkt";var ms=us("CiFwdWJsaWMvdjEvdXNlcl9wYW5lbF9jb3Vyc2UucHJvdG8SDXB1YmxpY19hcGkudjEiRAoPUGFuZWxMb2NrUmVhc29uEhYKDmdhdGluZ190ZXN0X2lkGAEgASgJEhkKEWdhdGluZ190ZXN0X3RpdGxlGAIgASgJIrQCCg9QYW5lbENvdXJzZUl0ZW0SCgoCaWQYASABKAkSMAoEa2luZBgCIAEoDjIiLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VJdGVtS2luZBINCgV0aXRsZRgDIAEoCRIPCgdpc19mcmVlGAQgASgIEg4KBmxvY2tlZBgFIAEoCBI4Cgtsb2NrX3JlYXNvbhgGIAEoCzIeLnB1YmxpY19hcGkudjEuUGFuZWxMb2NrUmVhc29uSACIAQESEQoJY29tcGxldGVkGAcgASgIEhgKEGR1cmF0aW9uX3NlY29uZHMYCCABKAUSGgoNdGh1bWJuYWlsX3VybBgJIAEoCUgBiAEBEg4KBmdhdGluZxgKIAEoCEIOCgxfbG9ja19yZWFzb25CEAoOX3RodW1ibmFpbF91cmwicwoSUGFuZWxDb3Vyc2VTZWN0aW9uEgoKAmlkGAEgASgJEg0KBXRpdGxlGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEi0KBWl0ZW1zGAQgAygLMh4ucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUl0ZW0i3wIKC1BhbmVsQ291cnNlEgoKAmlkGAEgASgJEiwKBHR5cGUYAiABKA4yHi5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlVHlwZRINCgV0aXRsZRgDIAEoCRITCgtkZXNjcmlwdGlvbhgEIAEoCRIWCgljb3Zlcl91cmwYBSABKAlIAIgBARIVCg10b3RhbF9sZXNzb25zGAYgASgFEhkKEWNvbXBsZXRlZF9sZXNzb25zGAcgASgFEhgKEHByb2dyZXNzX3BlcmNlbnQYCCABKAUSMwoIc2VjdGlvbnMYCSADKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlU2VjdGlvbhIzCgtmaW5hbF9pdGVtcxgKIAMoCzIeLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VJdGVtEhYKDnJlc3VtZV9pdGVtX2lkGAsgASgJQgwKCl9jb3Zlcl91cmwiRAoZVXNlclBhbmVsR2V0Q291cnNlUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJIosBChpVc2VyUGFuZWxHZXRDb3Vyc2VSZXNwb25zZRItCgdzdWNjZXNzGAEgASgLMhoucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAQggKBnJlc3VsdCJfCgpQYW5lbFZpZGVvEg8KB2hsc191cmwYASABKAkSFwoKcG9zdGVyX3VybBgCIAEoCUgAiAEBEhgKEGR1cmF0aW9uX3NlY29uZHMYAyABKAVCDQoLX3Bvc3Rlcl91cmwiRgoPUGFuZWxBdHRhY2htZW50EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSDAoEc2l6ZRgDIAEoAxILCgN1cmwYBCABKAkiTAoMUGFuZWxJdGVtUmVmEgoKAmlkGAEgASgJEjAKBGtpbmQYAiABKA4yIi5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlSXRlbUtpbmQigQQKC1BhbmVsTGVzc29uEgoKAmlkGAEgASgJEhIKCnNlY3Rpb25faWQYAiABKAkSDQoFdGl0bGUYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSFAoMY29udGVudF9odG1sGAUgASgJEi0KBXZpZGVvGAYgASgLMhkucHVibGljX2FwaS52MS5QYW5lbFZpZGVvSACIAQESMwoLYXR0YWNobWVudHMYByADKAsyHi5wdWJsaWNfYXBpLnYxLlBhbmVsQXR0YWNobWVudBIuCgRwcmV2GAggASgLMhsucHVibGljX2FwaS52MS5QYW5lbEl0ZW1SZWZIAYgBARIuCgRuZXh0GAkgASgLMhsucHVibGljX2FwaS52MS5QYW5lbEl0ZW1SZWZIAogBARIRCgljb21wbGV0ZWQYCiABKAgSGAoQcG9zaXRpb25fc2Vjb25kcxgLIAEoBRIRCglteV9yYXRpbmcYDCABKAUSFgoOYXZlcmFnZV9yYXRpbmcYDSABKAISFAoMcmF0aW5nX2NvdW50GA4gASgFEjcKDnVybHNfZXhwaXJlX2F0GA8gASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgDiAEBQggKBl92aWRlb0IHCgVfcHJldkIHCgVfbmV4dEIRCg9fdXJsc19leHBpcmVfYXQiVwoZVXNlclBhbmVsR2V0TGVzc29uUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEhEKCWxlc3Nvbl9pZBgDIAEoCSKLAQoaVXNlclBhbmVsR2V0TGVzc29uUmVzcG9uc2USLQoHc3VjY2VzcxgBIAEoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25IABI0CgdmYWlsdXJlGAIgASgOMiEucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUZhaWx1cmVIAEIICgZyZXN1bHQipwEKIlVzZXJQYW5lbFNhdmVMZXNzb25Qcm9ncmVzc1JlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIRCglsZXNzb25faWQYAyABKAkSEQoJY29tcGxldGVkGAQgASgIEhgKEHBvc2l0aW9uX3NlY29uZHMYBSABKAUSGAoQZHVyYXRpb25fc2Vjb25kcxgGIAEoBSKFAgojVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2USTAoHc3VjY2VzcxgBIAEoCzI5LnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaUAoGUmVzdWx0EhEKCWNvbXBsZXRlZBgBIAEoCBIZChFjb21wbGV0ZWRfbGVzc29ucxgCIAEoBRIYChBwcm9ncmVzc19wZXJjZW50GAMgASgFQggKBnJlc3VsdCIoCgtQYW5lbENob2ljZRIKCgJpZBgBIAEoCRINCgVsYWJlbBgCIAEoCSJPCg5QYW5lbE1jcUNvbmZpZxIrCgdjaG9pY2VzGAEgAygLMhoucHVibGljX2FwaS52MS5QYW5lbENob2ljZRIQCghtdWx0aXBsZRgCIAEoCCKCAQoTUGFuZWxUb2RvbGlzdENvbmZpZxI2CgVzdGVwcxgBIAMoCzInLnB1YmxpY19hcGkudjEuUGFuZWxUb2RvbGlzdENvbmZpZy5TdGVwGjMKBFN0ZXASCgoCaWQYASABKAkSDQoFbGFiZWwYAiABKAkSEAoIcmVxdWlyZWQYAyABKAgiNgoSUGFuZWxGaWxsR2FwQ29uZmlnEhAKCHRlbXBsYXRlGAEgASgJEg4KBmJsYW5rcxgCIAMoCSJBChRQYW5lbFNvcnRPcmRlckNvbmZpZxIpCgVpdGVtcxgBIAMoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxDaG9pY2UioQIKEVBhbmVsVGVzdFF1ZXN0aW9uEgoKAmlkGAEgASgJEg4KBnByb21wdBgCIAEoCRIOCgZwb2ludHMYAyABKAUSLAoDbWNxGAogASgLMh0ucHVibGljX2FwaS52MS5QYW5lbE1jcUNvbmZpZ0gAEjYKCHRvZG9saXN0GAsgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRvZG9saXN0Q29uZmlnSAASNQoIZmlsbF9nYXAYDCABKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsRmlsbEdhcENvbmZpZ0gAEjkKCnNvcnRfb3JkZXIYDSABKAsyIy5wdWJsaWNfYXBpLnYxLlBhbmVsU29ydE9yZGVyQ29uZmlnSABCCAoGY29uZmlnIqsCCglQYW5lbFRlc3QSCgoCaWQYASABKAkSDQoFdGl0bGUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSDgoGZ2F0aW5nGAQgASgIEh4KFnBhc3NfdGhyZXNob2xkX3BlcmNlbnQYBSABKAUSGAoQYXR0ZW1wdHNfYWxsb3dlZBgGIAEoBRIaCg1hdHRlbXB0c19sZWZ0GAcgASgFSACIAQESFQoNYXR0ZW1wdF9jb3VudBgIIAEoBRIaChJiZXN0X3Njb3JlX3BlcmNlbnQYCSABKAUSDgoGcGFzc2VkGAogASgIEjMKCXF1ZXN0aW9ucxgLIAMoCzIgLnB1YmxpY19hcGkudjEuUGFuZWxUZXN0UXVlc3Rpb25CEAoOX2F0dGVtcHRzX2xlZnQiUwoXVXNlclBhbmVsR2V0VGVzdFJlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIPCgd0ZXN0X2lkGAMgASgJIocBChhVc2VyUGFuZWxHZXRUZXN0UmVzcG9uc2USKwoHc3VjY2VzcxgBIAEoCzIYLnB1YmxpY19hcGkudjEuUGFuZWxUZXN0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSABCCAoGcmVzdWx0IiYKDlBhbmVsTWNxQW5zd2VyEhQKDHNlbGVjdGVkX2lkcxgBIAMoCSIqChNQYW5lbFRvZG9saXN0QW5zd2VyEhMKC2NoZWNrZWRfaWRzGAEgAygJIoIBChJQYW5lbEZpbGxHYXBBbnN3ZXISPQoGdmFsdWVzGAEgAygLMi0ucHVibGljX2FwaS52MS5QYW5lbEZpbGxHYXBBbnN3ZXIuVmFsdWVzRW50cnkaLQoLVmFsdWVzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASIrChRQYW5lbFNvcnRPcmRlckFuc3dlchITCgtvcmRlcmVkX2lkcxgBIAMoCSKDAgoLUGFuZWxBbnN3ZXISEwoLcXVlc3Rpb25faWQYASABKAkSLAoDbWNxGAogASgLMh0ucHVibGljX2FwaS52MS5QYW5lbE1jcUFuc3dlckgAEjYKCHRvZG9saXN0GAsgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRvZG9saXN0QW5zd2VySAASNQoIZmlsbF9nYXAYDCABKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsRmlsbEdhcEFuc3dlckgAEjkKCnNvcnRfb3JkZXIYDSABKAsyIy5wdWJsaWNfYXBpLnYxLlBhbmVsU29ydE9yZGVyQW5zd2VySABCBwoFdmFsdWUijQEKE1BhbmVsVGVzdFN1Ym1pc3Npb24SCgoCaWQYASABKAkSFQoNc2NvcmVfcGVyY2VudBgCIAEoBRIOCgZwYXNzZWQYAyABKAgSEwoLZHVyYXRpb25fbXMYBCABKAMSLgoKY3JlYXRlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAilwEKE1BhbmVsUXVlc3Rpb25SZXN1bHQSEwoLcXVlc3Rpb25faWQYASABKAkSDwoHY29ycmVjdBgCIAEoCBIOCgZwb2ludHMYAyABKAUSNwoOY29ycmVjdF9hbnN3ZXIYBCABKAsyGi5wdWJsaWNfYXBpLnYxLlBhbmVsQW5zd2VySACIAQFCEQoPX2NvcnJlY3RfYW5zd2VyIpgBChpVc2VyUGFuZWxTdWJtaXRUZXN0UmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEg8KB3Rlc3RfaWQYAyABKAkSKwoHYW5zd2VycxgEIAMoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxBbnN3ZXISEwoLZHVyYXRpb25fbXMYBSABKAMiyQIKG1VzZXJQYW5lbFN1Ym1pdFRlc3RSZXNwb25zZRJECgdzdWNjZXNzGAEgASgLMjEucHVibGljX2FwaS52MS5Vc2VyUGFuZWxTdWJtaXRUZXN0UmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaowEKBlJlc3VsdBI2CgpzdWJtaXNzaW9uGAEgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRlc3RTdWJtaXNzaW9uEjMKB3Jlc3VsdHMYAiADKAsyIi5wdWJsaWNfYXBpLnYxLlBhbmVsUXVlc3Rpb25SZXN1bHQSGgoNYXR0ZW1wdHNfbGVmdBgDIAEoBUgAiAEBQhAKDl9hdHRlbXB0c19sZWZ0QggKBnJlc3VsdCJfCiNVc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEg8KB3Rlc3RfaWQYAyABKAkiqwIKJFVzZXJQYW5lbExpc3RUZXN0U3VibWlzc2lvbnNSZXNwb25zZRJNCgdzdWNjZXNzGAEgASgLMjoucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAadAoGUmVzdWx0EjcKC3N1Ym1pc3Npb25zGAEgAygLMiIucHVibGljX2FwaS52MS5QYW5lbFRlc3RTdWJtaXNzaW9uEhoKEmJlc3Rfc2NvcmVfcGVyY2VudBgCIAEoBRIVCg1hdHRlbXB0X2NvdW50GAMgASgFQggKBnJlc3VsdCJWChNQYW5lbFF1ZXN0aW9uQW5zd2VyEg8KB2NvbnRlbnQYASABKAkSLgoKY3JlYXRlZF9hdBgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAizAEKE1BhbmVsTGVzc29uUXVlc3Rpb24SCgoCaWQYASABKAkSDwoHY29udGVudBgCIAEoCRITCgthdXRob3JfbmFtZRgDIAEoCRIPCgdpc19taW5lGAQgASgIEi4KCmNyZWF0ZWRfYXQYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjcKBmFuc3dlchgGIAEoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxRdWVzdGlvbkFuc3dlckgAiAEBQgkKB19hbnN3ZXIiYQojVXNlclBhbmVsTGlzdExlc3NvblF1ZXN0aW9uc1JlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIRCglsZXNzb25faWQYAyABKAki9gEKJFVzZXJQYW5lbExpc3RMZXNzb25RdWVzdGlvbnNSZXNwb25zZRJNCgdzdWNjZXNzGAEgASgLMjoucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0TGVzc29uUXVlc3Rpb25zUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaPwoGUmVzdWx0EjUKCXF1ZXN0aW9ucxgBIAMoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25RdWVzdGlvbkIICgZyZXN1bHQicAohVXNlclBhbmVsQXNrTGVzc29uUXVlc3Rpb25SZXF1ZXN0EhQKDGFjY2Vzc190b2tlbhgBIAEoCRIRCglhY2Nlc3NfaWQYAiABKAkSEQoJbGVzc29uX2lkGAMgASgJEg8KB2NvbnRlbnQYBCABKAkimwEKIlVzZXJQYW5lbEFza0xlc3NvblF1ZXN0aW9uUmVzcG9uc2USNQoHc3VjY2VzcxgBIAEoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25RdWVzdGlvbkgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAQggKBnJlc3VsdCJ4ChpVc2VyUGFuZWxSYXRlTGVzc29uUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEhEKCWxlc3Nvbl9pZBgDIAEoCRINCgVzY29yZRgEIAEoBRIPCgdjb21tZW50GAUgASgJIu4BChtVc2VyUGFuZWxSYXRlTGVzc29uUmVzcG9uc2USRAoHc3VjY2VzcxgBIAEoCzIxLnB1YmxpY19hcGkudjEuVXNlclBhbmVsUmF0ZUxlc3NvblJlc3BvbnNlLlJlc3VsdEgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAGkkKBlJlc3VsdBIRCglteV9yYXRpbmcYASABKAUSFgoOYXZlcmFnZV9yYXRpbmcYAiABKAISFAoMcmF0aW5nX2NvdW50GAMgASgFQggKBnJlc3VsdCr6AgoSUGFuZWxDb3Vyc2VGYWlsdXJlEiAKHFBBTkVMX0NPVVJTRV9GQUlMVVJFX1VOS05PV04QABIlCiFQQU5FTF9DT1VSU0VfRkFJTFVSRV9VTkFVVEhPUklaRUQQARIpCiVQQU5FTF9DT1VSU0VfRkFJTFVSRV9DT1VSU0VfTk9UX0ZPVU5EEAISJwojUEFORUxfQ09VUlNFX0ZBSUxVUkVfSVRFTV9OT1RfRk9VTkQQAxIkCiBQQU5FTF9DT1VSU0VfRkFJTFVSRV9JVEVNX0xPQ0tFRBAEEikKJVBBTkVMX0NPVVJTRV9GQUlMVVJFX05PX0FUVEVNUFRTX0xFRlQQBRIpCiVQQU5FTF9DT1VSU0VfRkFJTFVSRV9JTlZBTElEX0FSR1VNRU5UEAYSJQohUEFORUxfQ09VUlNFX0ZBSUxVUkVfUkFURV9MSU1JVEVEEAcSJAogUEFORUxfQ09VUlNFX0ZBSUxVUkVfVU5BVkFJTEFCTEUQCCptCg9QYW5lbENvdXJzZVR5cGUSHQoZUEFORUxfQ09VUlNFX1RZUEVfVU5LTk9XThAAEh4KGlBBTkVMX0NPVVJTRV9UWVBFX1NUQU5EQVJEEAESGwoXUEFORUxfQ09VUlNFX1RZUEVfVklERU8QAip9ChNQYW5lbENvdXJzZUl0ZW1LaW5kEiIKHlBBTkVMX0NPVVJTRV9JVEVNX0tJTkRfVU5LTk9XThAAEiEKHVBBTkVMX0NPVVJTRV9JVEVNX0tJTkRfTEVTU09OEAESHwobUEFORUxfQ09VUlNFX0lURU1fS0lORF9URVNUEAIy+QcKFlVzZXJQYW5lbENvdXJzZVNlcnZpY2USYAoJR2V0Q291cnNlEigucHVibGljX2FwaS52MS5Vc2VyUGFuZWxHZXRDb3Vyc2VSZXF1ZXN0GikucHVibGljX2FwaS52MS5Vc2VyUGFuZWxHZXRDb3Vyc2VSZXNwb25zZRJgCglHZXRMZXNzb24SKC5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldExlc3NvblJlcXVlc3QaKS5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldExlc3NvblJlc3BvbnNlEnsKElNhdmVMZXNzb25Qcm9ncmVzcxIxLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVxdWVzdBoyLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2USWgoHR2V0VGVzdBImLnB1YmxpY19hcGkudjEuVXNlclBhbmVsR2V0VGVzdFJlcXVlc3QaJy5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldFRlc3RSZXNwb25zZRJjCgpTdWJtaXRUZXN0EikucHVibGljX2FwaS52MS5Vc2VyUGFuZWxTdWJtaXRUZXN0UmVxdWVzdBoqLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU3VibWl0VGVzdFJlc3BvbnNlEn4KE0xpc3RUZXN0U3VibWlzc2lvbnMSMi5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbExpc3RUZXN0U3VibWlzc2lvbnNSZXF1ZXN0GjMucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVzcG9uc2USfgoTTGlzdExlc3NvblF1ZXN0aW9ucxIyLnB1YmxpY19hcGkudjEuVXNlclBhbmVsTGlzdExlc3NvblF1ZXN0aW9uc1JlcXVlc3QaMy5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbExpc3RMZXNzb25RdWVzdGlvbnNSZXNwb25zZRJ4ChFBc2tMZXNzb25RdWVzdGlvbhIwLnB1YmxpY19hcGkudjEuVXNlclBhbmVsQXNrTGVzc29uUXVlc3Rpb25SZXF1ZXN0GjEucHVibGljX2FwaS52MS5Vc2VyUGFuZWxBc2tMZXNzb25RdWVzdGlvblJlc3BvbnNlEmMKClJhdGVMZXNzb24SKS5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbFJhdGVMZXNzb25SZXF1ZXN0GioucHVibGljX2FwaS52MS5Vc2VyUGFuZWxSYXRlTGVzc29uUmVzcG9uc2ViBnByb3RvMw",[ps]);var Rt=ds(ms,0);import{Code as ce,ConnectError as gs}from"@connectrpc/connect";import{timestampDate as Ne}from"@bufbuild/protobuf/wkt";var B=class extends Error{code;constructor(t,s){super(s??t),this.name="CourseError",this.code=t}};var bs={0:"unknown",1:"unauthorized",2:"course_not_found",3:"item_not_found",4:"item_locked",5:"no_attempts_left",6:"invalid_argument",7:"rate_limited",8:"unavailable"};function fs(e){return bs[e]??"unknown"}function xs(e){if(e instanceof B)return e;let t=gs.from(e);switch(t.code){case ce.Unauthenticated:case ce.PermissionDenied:return new B("unauthorized",t.rawMessage);case ce.NotFound:return new B("course_not_found",t.rawMessage);case ce.ResourceExhausted:return new B("rate_limited",t.rawMessage);case ce.Unavailable:case ce.DeadlineExceeded:return new B("unavailable",t.rawMessage);default:return e}}function K(e){let{result:t}=e;if(t.case==="success")return t.value;throw t.case==="failure"?new B(fs(t.value)):new B("unknown","empty response")}function kt(e){return{id:e.id,kind:e.kind===2?"test":"lesson",title:e.title,isFree:e.isFree,locked:e.locked,lockReason:e.locked&&e.lockReason!=null?{gatingTestId:e.lockReason.gatingTestId,gatingTestTitle:e.lockReason.gatingTestTitle}:void 0,completed:e.completed,durationSeconds:e.durationSeconds,thumbnailUrl:e.thumbnailUrl||void 0,gating:e.gating}}function As(e){return{id:e.id,type:e.type===2?"video":"standard",title:e.title,description:e.description,coverUrl:e.coverUrl||void 0,totalLessons:e.totalLessons,completedLessons:e.completedLessons,progressPercent:e.progressPercent,sections:e.sections.map(t=>({id:t.id,title:t.title,description:t.description,items:t.items.map(kt)})),finalItems:e.finalItems.map(kt),resumeItemId:e.resumeItemId}}function Nt(e){return e==null?void 0:{id:e.id,kind:e.kind===2?"test":"lesson"}}function ys(e){return{id:e.id,sectionId:e.sectionId,title:e.title,description:e.description,contentHtml:e.contentHtml,video:e.video!=null&&e.video.hlsUrl!==""?{hlsUrl:e.video.hlsUrl,posterUrl:e.video.posterUrl||void 0,durationSeconds:e.video.durationSeconds}:void 0,attachments:e.attachments.map(t=>({id:t.id,name:t.name,size:Number(t.size),url:t.url})),prev:Nt(e.prev),next:Nt(e.next),completed:e.completed,positionSeconds:e.positionSeconds,myRating:e.myRating,averageRating:e.averageRating,ratingCount:e.ratingCount,urlsExpireAt:e.urlsExpireAt!=null?Ne(e.urlsExpireAt):void 0}}function Ss(e){let t={id:e.id,prompt:e.prompt,points:e.points},{config:s}=e;switch(s.case){case"mcq":return{...t,config:{type:"mcq",choices:s.value.choices.map(o=>({id:o.id,label:o.label})),multiple:s.value.multiple}};case"todolist":return{...t,config:{type:"todolist",steps:s.value.steps.map(o=>({id:o.id,label:o.label,required:o.required}))}};case"fillGap":return{...t,config:{type:"fill_gap",template:s.value.template,blanks:[...s.value.blanks]}};case"sortOrder":return{...t,config:{type:"sort_order",items:s.value.items.map(o=>({id:o.id,label:o.label}))}};default:return}}function hs(e){return{id:e.id,title:e.title,description:e.description,gating:e.gating,passThresholdPercent:e.passThresholdPercent,attemptsAllowed:e.attemptsAllowed,attemptsLeft:e.attemptsLeft,attemptCount:e.attemptCount,bestScorePercent:e.bestScorePercent,passed:e.passed,questions:e.questions.map(Ss).filter(t=>t!=null)}}function Pt(e){return{id:e.id,scorePercent:e.scorePercent,passed:e.passed,durationMs:Number(e.durationMs),createdAt:e.createdAt!=null?Ne(e.createdAt):void 0}}function vs(e){let{value:t}=e;switch(t.case){case"mcq":return{type:"mcq",selectedIds:[...t.value.selectedIds]};case"todolist":return{type:"todolist",checkedIds:[...t.value.checkedIds]};case"fillGap":return{type:"fill_gap",values:{...t.value.values}};case"sortOrder":return{type:"sort_order",orderedIds:[...t.value.orderedIds]};default:return}}function Cs(e,t){switch(t.type){case"mcq":return{questionId:e,value:{case:"mcq",value:{selectedIds:t.selectedIds}}};case"todolist":return{questionId:e,value:{case:"todolist",value:{checkedIds:t.checkedIds}}};case"fill_gap":return{questionId:e,value:{case:"fillGap",value:{values:t.values}}};case"sort_order":return{questionId:e,value:{case:"sortOrder",value:{orderedIds:t.orderedIds}}}}}function Vt(e){return{id:e.id,content:e.content,authorName:e.authorName,isMine:e.isMine,createdAt:e.createdAt!=null?Ne(e.createdAt):void 0,answer:e.answer!=null?{content:e.answer.content,createdAt:e.answer.createdAt!=null?Ne(e.answer.createdAt):void 0}:void 0}}function Le(e,t){let s=()=>({accessId:t.accessId,accessToken:typeof t.accessToken=="function"?t.accessToken()??"":t.accessToken});async function o(n){try{return await n()}catch(a){throw xs(a)}}return{getCourse:()=>o(async()=>As(K(await e.getCourse(s())))),getLesson:n=>o(async()=>ys(K(await e.getLesson({...s(),lessonId:n})))),saveProgress:n=>o(async()=>{let a=K(await e.saveLessonProgress({...s(),...n}));return{completed:a.completed,completedLessons:a.completedLessons,progressPercent:a.progressPercent}}),getTest:n=>o(async()=>hs(K(await e.getTest({...s(),testId:n})))),submitTest:(n,a,r)=>o(async()=>{let i=K(await e.submitTest({...s(),testId:n,durationMs:BigInt(Math.max(0,Math.round(r))),answers:Object.entries(a).map(([u,b])=>Cs(u,b))}));if(i.submission==null)throw new B("unknown","missing submission");return{submission:Pt(i.submission),results:i.results.map(u=>({questionId:u.questionId,correct:u.correct,points:u.points,correctAnswer:u.correctAnswer!=null?vs(u.correctAnswer):void 0})),attemptsLeft:i.attemptsLeft}}),listSubmissions:n=>o(async()=>{let a=K(await e.listTestSubmissions({...s(),testId:n}));return{submissions:a.submissions.map(Pt),bestScorePercent:a.bestScorePercent,attemptCount:a.attemptCount}}),listQuestions:n=>o(async()=>K(await e.listLessonQuestions({...s(),lessonId:n})).questions.map(Vt)),askQuestion:(n,a)=>o(async()=>Vt(K(await e.askLessonQuestion({...s(),lessonId:n,content:a})))),rate:(n,a,r="")=>o(async()=>{let i=K(await e.rateLesson({...s(),lessonId:n,score:a,comment:r}));return{myRating:i.myRating,averageRating:i.averageRating,ratingCount:i.ratingCount}})}}function Be(e){let t=e.host??st(),s=Rs(Rt,nt(t));return Le(s,{accessId:e.accessId,accessToken:e.accessToken??(()=>{let{projectID:o,host:n}=rt();return it(o,n)})})}import{ChevronLeft as Ls,ChevronRight as Bs,Download as Zs,Paperclip as Xs}from"lucide-react";import{useEffect as zt,useId as Ms,useMemo as zs,useRef as We,useState as ye}from"react";import{useState as ee}from"react";import{Star as ks}from"lucide-react";import{useState as Ns}from"react";import{jsx as Pe}from"react/jsx-runtime";function Tt({value:e,onSelect:t,disabled:s}){let{texts:o}=C(),[n,a]=Ns(0),r=n||e;return Pe("div",{className:"flex items-center gap-1",role:t?"radiogroup":"img","aria-label":I(o.rateStar,{n:Math.round(e)}),children:[1,2,3,4,5].map(i=>{let u=i<=Math.round(r),b=Pe(ks,{className:y("size-5",u?"fill-amber-400 text-amber-400":"text-neutral-300 dark:text-neutral-600"),"aria-hidden":!0});return t?Pe("button",{type:"button",role:"radio","aria-checked":i===e,"aria-label":I(o.rateStar,{n:i}),disabled:s,className:"rounded p-0.5 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50",onMouseEnter:()=>a(i),onMouseLeave:()=>a(0),onClick:()=>t(i),children:b},i):Pe("span",{children:b},i)})})}import{AlertTriangle as Ps}from"lucide-react";import{jsx as D,jsxs as Ze}from"react/jsx-runtime";var wt="bg-[var(--course-accent,var(--primary))] text-[var(--course-accent-fg,var(--primary-foreground))] dark:bg-[var(--course-accent-dark,var(--course-accent,var(--primary)))] dark:text-[var(--course-accent-dark-fg,var(--course-accent-fg,var(--primary-foreground)))]",H=y(wt,"hover:bg-[var(--course-accent,var(--primary))] hover:opacity-90 dark:hover:bg-[var(--course-accent-dark,var(--course-accent,var(--primary)))]"),z="rounded-xl bg-white p-4 drop-shadow-xl sm:p-6 dark:border dark:border-neutral-700 dark:bg-neutral-900 dark:drop-shadow-none";function q(e,t){if(e instanceof B)switch(e.code){case"unauthorized":return t.errorUnauthorized;case"course_not_found":case"item_not_found":return t.errorNotFound;case"unavailable":return t.errorUnavailable}return t.errorGeneric}function oe({error:e,onRetry:t,className:s}){let{texts:o}=C();return Ze("div",{role:"alert",className:y("flex flex-col items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700 dark:border-red-900 dark:bg-red-950 dark:text-red-300",s),children:[Ze("p",{className:"flex items-center gap-2 font-medium",children:[D(Ps,{className:"size-4 shrink-0","aria-hidden":!0}),q(e,o)]}),t&&D(T,{variant:"outline",size:"sm",onClick:t,children:o.retry})]})}function Ae({label:e}){let{texts:t}=C();return Ze("div",{role:"status","aria-label":e??t.loading,className:"space-y-3",children:[D(fe,{className:"aspect-video w-full"}),D(fe,{className:"h-6 w-2/3"}),D(fe,{className:"h-4 w-full"}),D(fe,{className:"h-4 w-5/6"})]})}function It({percent:e,label:t}){let s=Math.max(0,Math.min(100,Math.round(e)));return D("div",{role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":s,"aria-label":t,className:"h-2 w-full overflow-hidden rounded-full bg-neutral-200 dark:bg-neutral-700",children:D("div",{className:y("h-full rounded-full transition-all duration-300",wt),style:{width:`${s}%`}})})}import{useCallback as Xe,useEffect as Vs,useRef as Me,useState as Et}from"react";function O(e,t,s){let[o,n]=Et(s!==void 0?{status:"success",data:s}:{status:"loading"}),[a,r]=Et(0),i=Me(0),u=Me(e);u.current=e;let b=Me(s!==void 0);Vs(()=>{if(b.current){b.current=!1;return}let p=++i.current;return n({status:"loading"}),u.current().then(f=>{p===i.current&&n({status:"success",data:f})}).catch(f=>{p===i.current&&n({status:"error",error:f})}),()=>{i.current+=1}},[...t,a]);let A=Xe(()=>r(p=>p+1),[]),V=Xe(()=>{let p=++i.current;u.current().then(f=>{p===i.current&&n({status:"success",data:f})}).catch(()=>{})},[]),l=Xe(p=>{n(f=>f.status==="success"?{status:"success",data:p(f.data)}:f)},[]);return{...o,reload:A,refresh:V,mutate:l}}import{jsx as G,jsxs as $}from"react/jsx-runtime";function Ut({lessonId:e,initial:t}){let{dataSource:s,texts:o,preview:n}=C(),a=F(),[r,i]=ee(t),[u,b]=ee(t.myRating),[A,V]=ee(""),[l,p]=ee("idle"),[f,d]=ee();async function N(){if(!(u<1)){p("saving");try{i(await s.rate(e,u,A.trim())),p("saved"),a("lesson_rated",{lesson_id:e,rating:u})}catch(v){d(v),p("error")}}}return $("section",{"aria-labelledby":`rate-${e}`,className:"space-y-3",children:[G("h3",{id:`rate-${e}`,className:"text-lg font-semibold",children:o.rateTitle}),G(Tt,{value:u,onSelect:v=>{b(v),p("idle")},disabled:n||l==="saving"}),r.ratingCount>0&&G("p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:I(o.rateAverage,{average:r.averageRating.toFixed(1),count:r.ratingCount})}),!n&&u>0&&$("div",{className:"space-y-2",children:[G(Ie,{value:A,maxLength:1e3,placeholder:o.ratePlaceholder,onChange:v=>{V(v.target.value),p("idle")}}),G(T,{size:"sm",className:H,disabled:l==="saving",onClick:N,children:o.rateSubmit})]}),l==="saved"&&G("p",{role:"status",className:"text-sm text-emerald-700 dark:text-emerald-400",children:o.rateSaved}),l==="error"&&G("p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:q(f,o)})]})}function Gt(e){return e==null?"":e.toLocaleDateString()}function Lt({lessonId:e}){let{dataSource:t,texts:s,preview:o}=C(),n=O(()=>t.listQuestions(e),[t,e]),[a,r]=ee(""),[i,u]=ee("idle"),[b,A]=ee();async function V(){let l=a.trim();if(l!==""){u("sending");try{let p=await t.askQuestion(e,l);n.mutate(f=>[p,...f]),r(""),u("sent")}catch(p){A(p),u("error")}}}return $("section",{"aria-labelledby":`qa-${e}`,className:"space-y-4",children:[G("h3",{id:`qa-${e}`,className:"text-lg font-semibold",children:s.questionsTitle}),$("form",{className:"space-y-2",onSubmit:l=>{l.preventDefault(),V()},children:[G(Ie,{value:a,maxLength:2e3,placeholder:s.questionPlaceholder,"aria-label":s.questionPlaceholder,onChange:l=>{r(l.target.value),u("idle")}}),G(T,{type:"submit",size:"sm",className:H,disabled:i==="sending"||a.trim()==="",children:s.questionSubmit}),i==="sent"&&G("p",{role:"status",className:"text-sm text-emerald-700 dark:text-emerald-400",children:s.questionSent}),i==="error"&&G("p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:q(b,s)})]}),n.status==="loading"&&G("p",{role:"status",className:"text-sm text-neutral-500",children:s.loading}),n.status==="error"&&G(oe,{error:n.error,onRetry:n.reload}),n.status==="success"&&(n.data.length===0?G("p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:s.questionsEmpty}):G("ul",{className:"space-y-3",children:n.data.map(l=>$("li",{className:"rounded-lg bg-neutral-100 p-3 text-sm dark:bg-neutral-800",children:[$("p",{className:"mb-1 text-xs text-neutral-500 dark:text-neutral-400",children:[l.isMine?s.questionMine:l.authorName,l.createdAt&&` \xB7 ${Gt(l.createdAt)}`]}),G("p",{className:"break-words whitespace-pre-wrap",children:l.content}),l.answer&&$("div",{className:"mt-2 border-l-2 border-neutral-400 pl-3 dark:border-neutral-500",children:[$("p",{className:"mb-1 text-xs font-medium text-neutral-500 dark:text-neutral-400",children:[s.questionTeamAnswer,l.answer.createdAt&&` \xB7 ${Gt(l.answer.createdAt)}`]}),G("p",{className:"break-words whitespace-pre-wrap",children:l.answer.content})]})]},l.id))}))]})}import Ts from"dompurify";var ws={USE_PROFILES:{html:!0},FORBID_TAGS:["style","form","input","button","textarea","select","iframe","frame","object","embed","base","meta","link"],FORBID_ATTR:["style","srcdoc","formaction"],ALLOW_DATA_ATTR:!1,RETURN_TRUSTED_TYPE:!1};function Is(e){if(e.isSupported===!1||e.sanitize==null)return()=>"";let t=e.sanitize.bind(e);return e.addHook?.("afterSanitizeAttributes",s=>{s.tagName==="A"&&s.hasAttribute("href")&&(s.setAttribute("target","_blank"),s.setAttribute("rel","noopener noreferrer nofollow")),s.tagName==="IMG"&&(s.setAttribute("loading","lazy"),s.setAttribute("referrerpolicy","no-referrer"))}),s=>s==null||s===""?"":String(t(s,ws))}var Bt;function ze(e){return typeof window>"u"?"":(Bt??=Is(Ts),Bt(e))}import ue from"hls.js";import{useCallback as Es,useEffect as Gs,useRef as Ve,useState as Zt}from"react";import{jsx as Qe,jsxs as Xt}from"react/jsx-runtime";function Us(e){return e.canPlayType("application/vnd.apple.mpegurl")!==""}function Mt({src:e,poster:t,startAt:s=0,durationHint:o=0,autoPlay:n=!1,lessonId:a,onReport:r,onEnded:i,onExpired:u,className:b}){let{texts:A}=C(),V=F(),l=Ve(V);l.current=V;let p=Ve(null),[f,d]=Zt(!1),[N,v]=Zt(0),x=Ve({onReport:r,onEnded:i,onExpired:u,durationHint:o});x.current={onReport:r,onEnded:i,onExpired:u,durationHint:o};let W=Ve(void 0),L=Es((c,h=p.current)=>{if(h==null||x.current.onReport==null)return;let P=Math.floor(h.currentTime);if(P<=0&&c!=="ended")return;let Z=Number.isFinite(h.duration)?h.duration:x.current.durationHint;W.current=P,x.current.onReport({positionSeconds:P,durationSeconds:Math.floor(Z||0),reason:c})},[]);return Gs(()=>{let c=p.current;if(c==null)return;d(!1);let h,P=!1,Z=!1,k,X=()=>{s>0&&(c.currentTime=s),n&&c.play().catch(()=>{})};Us(c)?(c.src=e,c.addEventListener("loadedmetadata",X,{once:!0})):ue.isSupported()?(h=new ue,h.attachMedia(c),h.loadSource(e),h.on(ue.Events.MANIFEST_PARSED,X),h.on(ue.Events.ERROR,(_,M)=>{if(!M.fatal)return;let Ce=M.response?.code;Ce===401||Ce===403?(x.current.onExpired?.(),d(!0)):M.type===ue.ErrorTypes.NETWORK_ERROR&&!P?(P=!0,h?.startLoad()):M.type===ue.ErrorTypes.MEDIA_ERROR&&!Z?(Z=!0,h?.recoverMediaError()):d(!0)})):c.src=e;let te=()=>{clearInterval(k),k=setInterval(()=>L("periodic"),15e3)},he=()=>{clearInterval(k),c.ended||L("pause")},j=new Set,re=c.currentTime,ae=0,de=_=>{if(a!=null)for(let M of $e(_,j))l.current("video_progress",{lesson_id:a,milestone:M})},ie=()=>{let _=c.currentTime,M=Number.isFinite(c.duration)?c.duration:x.current.durationHint;M>0&&!c.seeking&&_>=re&&_-re<=2&&(ae=Math.max(ae,_),de(ae/M*100)),re=_},pe=()=>{clearInterval(k),a!=null&&!j.has(100)&&(j.add(100),l.current("video_progress",{lesson_id:a,milestone:100})),L("ended"),x.current.onEnded?.()},ve=()=>{h==null&&d(!0)};return c.addEventListener("play",te),c.addEventListener("pause",he),c.addEventListener("ended",pe),c.addEventListener("error",ve),c.addEventListener("timeupdate",ie),()=>{clearInterval(k),c.removeEventListener("play",te),c.removeEventListener("pause",he),c.removeEventListener("ended",pe),c.removeEventListener("error",ve),c.removeEventListener("timeupdate",ie),c.removeEventListener("loadedmetadata",X),!c.ended&&c.currentTime>0&&L("leave",c),h?.destroy(),c.removeAttribute("src"),c.load()}},[e,N]),Xt("div",{className:y("relative overflow-hidden rounded-xl bg-black",b),children:[Qe("video",{ref:p,className:"aspect-video w-full",poster:t,controls:!0,playsInline:!0,preload:"metadata",controlsList:"nodownload"}),f&&Xt("div",{role:"alert",className:"absolute inset-0 flex flex-col items-center justify-center gap-3 bg-black/80 p-4 text-center text-sm text-white",children:[Qe("p",{children:A.videoError}),Qe(T,{variant:"secondary",size:"sm",onClick:()=>{x.current.onExpired?.(),v(c=>c+1)},children:A.videoRetry})]})]})}import{jsx as R,jsxs as Q}from"react/jsx-runtime";function Qt({lesson:e,mobileOutline:t,nextItem:s,position:o=0,courseType:n,prev:a,next:r,autoplay:i,onAutoplayChange:u,startPlaying:b,onNavigate:A,onProgressSaved:V,onEnded:l,onUrlsExpired:p}){let{dataSource:f,texts:d,preview:N}=C(),v=F(),[x,W]=ye(e.completed),L=We(e.completed),c=We(void 0),h=We(e.positionSeconds),[P,Z]=ye(!1),[k,X]=ye(!1),[te,he]=ye(!1),j=Ms(),[re,ae]=ye(n==="video"?"lessons":"description");zt(()=>{v("lesson_started",{lesson_id:e.id,position:o})},[e.id]);let de=e.urlsExpireAt?.getTime();zt(()=>{if(de==null)return;let m=de-Date.now()-6e4;if(m<=0||m>2**31-1)return;let ne=setTimeout(p,m);return()=>clearTimeout(ne)},[de]);let ie=zs(()=>ze(e.contentHtml),[e.contentHtml]);async function pe(m,ne,Y){if(N)return;let be=await f.saveProgress({lessonId:e.id,completed:Y,positionSeconds:m,durationSeconds:ne});c.current=m,be.completed&&!L.current&&(L.current=!0,W(!0),he(!0),v("lesson_completed",{lesson_id:e.id,position:o})),V(e.id,be)}function ve(m){if(h.current=m.positionSeconds,N||m.reason==="periodic"&&m.positionSeconds===c.current)return;let ne=m.reason==="ended"||gt(m.positionSeconds,m.durationSeconds);pe(m.positionSeconds,m.durationSeconds,ne&&!L.current).catch(Y=>{console.warn("Saving the progress of the lesson failed",Y)})}async function _(){Z(!0);try{await pe(h.current,e.video?.durationSeconds??0,!0)}catch(m){console.warn("Marking the lesson as completed failed",m)}finally{Z(!1)}}let M=e.description.trim(),Ce=ie!==""||M!=="",se=[...n==="video"&&t?[{id:"lessons",label:d.lessonsTab??d.contents}]:[],{id:"description",label:d.descriptionTab??e.title},...e.attachments.length?[{id:"files",label:d.filesTab??d.attachments}]:[],{id:"questions",label:d.questionsTab??d.questionsTitle}],me=se.some(m=>m.id===re)?re:se[0].id,Re=m=>y(me!==m&&"hidden","lg:block"),ke=m=>({id:`${j}-panel-${m}`,role:"tabpanel","aria-labelledby":`${j}-tab-${m}`,tabIndex:0}),ge=s?.locked??!1,Dt=!N&&R(T,{size:"sm",variant:x?"outline":"default",className:y("min-h-11 h-auto whitespace-normal",!x&&H),disabled:x||P,onClick:_,children:x?d.markedComplete:d.markComplete}),Je=m=>Q("nav",{"aria-label":d.lessonNavigation??d.contents,"data-lesson-navigation":m?"mobile":"desktop",className:m?"fixed inset-x-0 bottom-[var(--course-mobile-bottom,0px)] z-40 flex items-center justify-between gap-2 border-t border-neutral-200 bg-white px-3 pt-2 pb-[max(0.5rem,env(safe-area-inset-bottom))] dark:border-neutral-700 dark:bg-neutral-900 lg:hidden":"hidden items-center justify-between gap-3 lg:flex",children:[Q(T,{variant:"outline",size:"sm",className:"min-h-11",disabled:a==null,onClick:()=>a&&A(a),children:[R(Ls,{"aria-hidden":!0}),m?d.mobilePrev??d.prevItem:d.prevItem]}),Dt,Q(T,{variant:"outline",size:"sm",className:"min-h-11",disabled:r==null||ge,onClick:()=>r&&!ge&&A(r),children:[m?d.mobileNext??d.nextItem:d.nextItem,R(Bs,{"aria-hidden":!0})]})]});return Q("article",{"data-lesson-id":e.id,className:"min-w-0 space-y-5 pb-[calc(5rem+env(safe-area-inset-bottom)+var(--course-mobile-bottom,0px))] lg:pb-0","aria-labelledby":`lesson-${e.id}`,children:[e.video&&R(Mt,{src:e.video.hlsUrl,poster:e.video.posterUrl,lessonId:e.id,startAt:bt(h.current,e.video.durationSeconds),durationHint:e.video.durationSeconds,autoPlay:b,onReport:ve,onEnded:()=>{X(!0),l()},onExpired:p}),Je(!1),R("div",{className:"lg:hidden",role:"tablist","aria-label":d.contents,children:R("div",{className:"flex overflow-x-auto border-b border-neutral-200 dark:border-neutral-700",children:se.map((m,ne)=>R("button",{type:"button",role:"tab",id:`${j}-tab-${m.id}`,"aria-selected":me===m.id,"aria-controls":`${j}-panel-${m.id}`,tabIndex:me===m.id?0:-1,className:y("min-h-11 min-w-11 shrink-0 border-b-2 px-4 text-sm font-medium focus-visible:outline focus-visible:outline-2",me===m.id?"border-current":"border-transparent text-neutral-600 dark:text-neutral-300"),onClick:()=>ae(m.id),onKeyDown:Y=>{let be=Y.key==="ArrowRight"?1:Y.key==="ArrowLeft"?-1:0;if(!be&&Y.key!=="Home"&&Y.key!=="End")return;Y.preventDefault();let De=Y.key==="Home"?0:Y.key==="End"?se.length-1:(ne+be+se.length)%se.length;ae(se[De].id),Y.currentTarget.parentElement?.querySelectorAll('[role="tab"]')[De]?.focus()},children:m.label},m.id))})}),n==="video"&&t&&R("div",{...ke("lessons"),className:y(z,me!=="lessons"&&"hidden","lg:hidden"),children:t}),Q("div",{className:y(z,"space-y-5"),children:[R("header",{className:"flex flex-wrap items-start justify-between gap-3",children:R("h2",{id:`lesson-${e.id}`,className:"text-2xl font-semibold break-words",children:e.title})}),n==="video"&&e.video&&Q("label",{className:"flex min-h-11 w-fit cursor-pointer items-center gap-2 text-sm text-neutral-600 dark:text-neutral-300",children:[R("input",{type:"checkbox",checked:i,onChange:m=>u(m.target.checked)}),d.autoplay]}),Q("div",{...ke("description"),className:Re("description"),children:[M!==""&&R("p",{className:"text-sm break-words whitespace-pre-wrap text-neutral-700 dark:text-neutral-300",children:M}),ie!==""&&R("div",{className:"bitsnap-course-html break-words [&_a]:underline [&_blockquote]:border-l-4 [&_blockquote]:pl-4 [&_h1]:text-2xl [&_h1]:font-semibold [&_h2]:text-xl [&_h2]:font-semibold [&_h3]:text-lg [&_h3]:font-semibold [&_img]:h-auto [&_img]:max-w-full [&_ol]:list-decimal [&_ol]:pl-6 [&_p]:my-3 [&_pre]:overflow-x-auto [&_table]:block [&_table]:max-w-full [&_table]:overflow-x-auto [&_ul]:list-disc [&_ul]:pl-6",dangerouslySetInnerHTML:{__html:ie}}),!Ce&&!e.video&&R("p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:d.noContent})]}),e.attachments.length>0&&R("div",{...ke("files"),className:Re("files"),children:e.attachments.length>0&&Q("section",{"aria-labelledby":`files-${e.id}`,className:"space-y-2",children:[Q("h3",{id:`files-${e.id}`,className:"flex items-center gap-2 text-lg font-semibold",children:[R(Xs,{className:"size-4","aria-hidden":!0}),d.attachments]}),R("ul",{className:"space-y-2",children:e.attachments.map(m=>R("li",{children:Q("a",{href:m.url,target:"_blank",rel:"noopener noreferrer",download:m.name,className:"flex min-h-11 items-center justify-between gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm transition hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-neutral-700",children:[Q("span",{className:"min-w-0 break-words",children:[m.name,R("span",{className:"ml-2 text-xs text-neutral-500 dark:text-neutral-400",children:ft(m.size)})]}),Q("span",{className:"flex shrink-0 items-center gap-1 text-xs font-medium",children:[d.download,R(Zs,{className:"size-3.5","aria-hidden":!0})]})]})},m.id))})]})})]}),R("div",{className:y(z,Re("description")),children:R(Ut,{lessonId:e.id,initial:{myRating:e.myRating,averageRating:e.averageRating,ratingCount:e.ratingCount}})}),R("div",{...ke("questions"),className:y(z,Re("questions")),children:R(Lt,{lessonId:e.id})}),R("p",{role:"status","aria-live":"polite",className:"text-sm text-emerald-700 dark:text-emerald-400",children:te?d.markedComplete:""}),(x||k)&&r&&s&&Q("section",{"data-course-up-next":!0,className:y(z,"space-y-3"),children:[R("h3",{className:"font-semibold break-words",children:I(d.upNext??`${d.nextItem}: {title}`,{title:s.title})}),ge&&R("p",{className:"text-sm text-neutral-600 dark:text-neutral-300",children:s.lockReason?I(d.lockedByTest,{test:s.lockReason.gatingTestTitle}):d.locked}),R(T,{className:y(H,"min-h-11"),disabled:ge,onClick:()=>!ge&&A(r),children:d.openNext??d.nextItem})]}),Je(!0)]})}var Wt={pl:{back:"Wr\xF3\u0107",loading:"\u0141adowanie kursu...",retry:"Spr\xF3buj ponownie",errorGeneric:"Nie uda\u0142o si\u0119 za\u0142adowa\u0107 danych. Spr\xF3buj ponownie za chwil\u0119.",errorUnauthorized:"Nie masz dost\u0119pu do tego kursu. Zaloguj si\u0119 ponownie lub skontaktuj si\u0119 z pomoc\u0105.",errorNotFound:"Nie znaleziono tego kursu lub lekcji.",errorUnavailable:"Kurs jest chwilowo niedost\u0119pny. Spr\xF3buj ponownie za chwil\u0119.",previewBadge:"Podgl\u0105d",progressLessons:"{done} z {total} lekcji",progressPercent:"{percent}% uko\u0144czono",contents:"Spis tre\u015Bci",free:"Darmowa",locked:"Zablokowane",lockedByTest:"Odblokuje je zaliczenie testu: {test}",lockedPage:"Ta lekcja jest zablokowana. Zalicz test \u201E{test}\u201D, aby j\u0105 odblokowa\u0107.",testBadge:"Test",completed:"Uko\u0144czono",continueCourse:"Kontynuuj kurs",noItem:"Wybierz lekcj\u0119 z listy.",lessonsTab:"Lekcje",descriptionTab:"Opis",filesTab:"Pliki",questionsTab:"Pytania",upNext:"Nast\u0119pna lekcja: {title}",openNext:"Przejd\u017A dalej",courseCompleted:"Kurs uko\u0144czony",lessonNavigation:"Nawigacja lekcji",mobilePrev:"Wstecz",mobileNext:"Dalej",markComplete:"Oznacz jako uko\u0144czon\u0105",markedComplete:"Lekcja uko\u0144czona",prevItem:"Poprzednia",nextItem:"Nast\u0119pna",attachments:"Materia\u0142y do pobrania",download:"Pobierz",autoplay:"Autoodtwarzanie nast\u0119pnej lekcji",videoError:"Nie uda\u0142o si\u0119 odtworzy\u0107 wideo.",videoRetry:"Od\u015Bwie\u017C wideo",noContent:"Ta lekcja nie ma jeszcze tre\u015Bci.",testGating:"Zaliczenie tego testu odblokowuje kolejne lekcje.",testAttemptsLeft:"Pozosta\u0142o pr\xF3b: {count}",testAttemptsUnlimited:"Liczba pr\xF3b nie jest ograniczona.",testNoAttemptsLeft:"Wykorzystano wszystkie pr\xF3by.",testPassThreshold:"Pr\xF3g zaliczenia: {percent}%",testBestScore:"Najlepszy wynik: {percent}%",testSubmit:"Sprawd\u017A odpowiedzi",testSubmitting:"Sprawdzanie...",testUnanswered:"Pytania bez odpowiedzi: {count}",testPassed:"Test zaliczony",testFailed:"Test niezaliczony",testScore:"Wynik: {percent}%",testCorrect:"Poprawnie",testIncorrect:"Niepoprawnie",testCorrectAnswer:"Poprawna odpowied\u017A",testTryAgain:"Spr\xF3buj ponownie",testPreviousAttempts:"Poprzednie podej\u015Bcia",testAttemptRow:"{percent}% \xB7 {date}",testNoQuestions:"Ten test nie ma jeszcze pyta\u0144.",mcqSingle:"Wybierz jedn\u0105 odpowied\u017A",mcqMultiple:"Zaznacz wszystkie poprawne odpowiedzi",todoHint:"Zaznacz wykonane kroki",todoRequired:"wymagany",gapPlaceholder:"uzupe\u0142nij",sortHint:"U\u0142\xF3\u017C elementy w poprawnej kolejno\u015Bci",moveUp:"Przesu\u0144 w g\xF3r\u0119",moveDown:"Przesu\u0144 w d\xF3\u0142",questionsTitle:"Pytania do lekcji",questionsEmpty:"Nikt jeszcze nie zada\u0142 pytania.",questionPlaceholder:"Zadaj pytanie autorowi kursu",questionSubmit:"Wy\u015Blij pytanie",questionSent:"Pytanie wys\u0142ane.",questionMine:"Ty",questionTeamAnswer:"Odpowied\u017A autora",rateTitle:"Oce\u0144 lekcj\u0119",rateStar:"{n} z 5",ratePlaceholder:"Komentarz (opcjonalnie)",rateSubmit:"Zapisz ocen\u0119",rateSaved:"Dzi\u0119kujemy za ocen\u0119.",rateAverage:"\u015Arednia ocena {average} ({count})"},en:{back:"Back",loading:"Loading the course...",retry:"Try again",errorGeneric:"Could not load the data. Please try again in a moment.",errorUnauthorized:"You do not have access to this course. Sign in again or contact support.",errorNotFound:"This course or lesson was not found.",errorUnavailable:"The course is temporarily unavailable. Please try again in a moment.",previewBadge:"Preview",progressLessons:"{done} of {total} lessons",progressPercent:"{percent}% complete",contents:"Contents",free:"Free",locked:"Locked",lockedByTest:"Pass the test to unlock it: {test}",lockedPage:"This lesson is locked. Pass the test \u201C{test}\u201D to unlock it.",testBadge:"Test",completed:"Completed",continueCourse:"Continue the course",noItem:"Pick a lesson from the list.",lessonsTab:"Lessons",descriptionTab:"Description",filesTab:"Files",questionsTab:"Questions",upNext:"Next lesson: {title}",openNext:"Continue",courseCompleted:"Course completed",lessonNavigation:"Lesson navigation",mobilePrev:"Previous",mobileNext:"Next",markComplete:"Mark as completed",markedComplete:"Lesson completed",prevItem:"Previous",nextItem:"Next",attachments:"Downloads",download:"Download",autoplay:"Autoplay the next lesson",videoError:"The video could not be played.",videoRetry:"Reload the video",noContent:"This lesson has no content yet.",testGating:"Passing this test unlocks the following lessons.",testAttemptsLeft:"Attempts left: {count}",testAttemptsUnlimited:"The number of attempts is unlimited.",testNoAttemptsLeft:"You have used all attempts.",testPassThreshold:"Passing score: {percent}%",testBestScore:"Best score: {percent}%",testSubmit:"Check answers",testSubmitting:"Checking...",testUnanswered:"Unanswered questions: {count}",testPassed:"Test passed",testFailed:"Test not passed",testScore:"Score: {percent}%",testCorrect:"Correct",testIncorrect:"Incorrect",testCorrectAnswer:"Correct answer",testTryAgain:"Try again",testPreviousAttempts:"Previous attempts",testAttemptRow:"{percent}% \xB7 {date}",testNoQuestions:"This test has no questions yet.",mcqSingle:"Choose one answer",mcqMultiple:"Select all correct answers",todoHint:"Check the steps you have done",todoRequired:"required",gapPlaceholder:"fill in",sortHint:"Put the items in the correct order",moveUp:"Move up",moveDown:"Move down",questionsTitle:"Questions about this lesson",questionsEmpty:"Nobody has asked a question yet.",questionPlaceholder:"Ask the course author a question",questionSubmit:"Send question",questionSent:"Question sent.",questionMine:"You",questionTeamAnswer:"Answer from the author",rateTitle:"Rate this lesson",rateStar:"{n} of 5",ratePlaceholder:"Comment (optional)",rateSubmit:"Save rating",rateSaved:"Thanks for your rating.",rateAverage:"Average rating {average} ({count})"}};function Ye(e,t=tt()){return et(Wt,t,e)}import{ArrowDown as Qs,ArrowUp as Ws,Check as Ft,X as _t}from"lucide-react";import{useRef as Yt,useState as Se}from"react";import{Fragment as Hs,jsx as g,jsxs as E}from"react/jsx-runtime";function Ys({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=C();if(e.config.type!=="mcq")return null;let{choices:a,multiple:r}=e.config;return E("fieldset",{className:"space-y-2",disabled:s,children:[g("legend",{className:"mb-2 text-xs text-neutral-500 dark:text-neutral-400",children:r?n.mcqMultiple:n.mcqSingle}),a.map(i=>{let u=t.selectedIds.includes(i.id);return E("label",{className:"flex cursor-pointer items-start gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[g("input",{className:"mt-0.5",type:r?"checkbox":"radio",name:`q-${e.id}`,checked:u,onChange:()=>o({type:"mcq",selectedIds:r?u?t.selectedIds.filter(b=>b!==i.id):[...t.selectedIds,i.id]:[i.id]})}),g("span",{className:"break-words",children:i.label})]},i.id)})]})}function Fs({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=C();return e.config.type!=="todolist"?null:E("fieldset",{className:"space-y-2",disabled:s,children:[g("legend",{className:"mb-2 text-xs text-neutral-500 dark:text-neutral-400",children:n.todoHint}),e.config.steps.map(a=>{let r=t.checkedIds.includes(a.id);return E("label",{className:"flex cursor-pointer items-start gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[g("input",{className:"mt-0.5",type:"checkbox",checked:r,onChange:()=>o({type:"todolist",checkedIds:r?t.checkedIds.filter(i=>i!==a.id):[...t.checkedIds,a.id]})}),E("span",{className:"break-words",children:[a.label,a.required&&E("span",{className:"ml-2 text-xs text-neutral-500 dark:text-neutral-400",children:["(",n.todoRequired,")"]})]})]},a.id)})]})}function _s({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=C();if(e.config.type!=="fill_gap")return null;let a=Ue(e.config.template,e.config.blanks);return g("p",{className:"text-sm leading-10 break-words",children:a.map((r,i)=>r.type==="text"?g("span",{className:"whitespace-pre-wrap",children:r.text},i):g(ot,{className:"mx-1 inline-block h-8 w-36 align-middle","aria-label":`${n.gapPlaceholder} ${r.id}`,placeholder:n.gapPlaceholder,disabled:s,value:t.values[r.id]??"",onChange:u=>o({type:"fill_gap",values:{...t.values,[r.id]:u.target.value}})},i))})}function Ks({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=C();if(e.config.type!=="sort_order")return null;let a=new Map(e.config.items.map(i=>[i.id,i.label])),r=(i,u)=>o({type:"sort_order",orderedIds:yt(t.orderedIds,i,u)});return E("div",{className:"space-y-2",children:[g("p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:n.sortHint}),g("ol",{className:"space-y-2",children:t.orderedIds.map((i,u)=>E("li",{className:"flex items-center gap-2 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[E("span",{className:"w-5 text-xs text-neutral-500",children:[u+1,"."]}),g("span",{className:"min-w-0 flex-1 break-words",children:a.get(i)??i}),g(T,{type:"button",variant:"ghost",size:"icon-xs","aria-label":n.moveUp,disabled:s||u===0,onClick:()=>r(u,u-1),children:g(Ws,{"aria-hidden":!0})}),g(T,{type:"button",variant:"ghost",size:"icon-xs","aria-label":n.moveDown,disabled:s||u===t.orderedIds.length-1,onClick:()=>r(u,u+1),children:g(Qs,{"aria-hidden":!0})})]},i))})]})}function js({question:e,answer:t,disabled:s,onChange:o}){let n={question:e,disabled:s,onChange:o};switch(t.type){case"mcq":return g(Ys,{...n,answer:t});case"todolist":return g(Fs,{...n,answer:t});case"fill_gap":return g(_s,{...n,answer:t});case"sort_order":return g(Ks,{...n,answer:t})}}function Js({list:e}){let{texts:t}=C();return e.submissions.length===0?null:E("section",{className:"space-y-2",children:[g("h3",{className:"text-sm font-semibold",children:t.testPreviousAttempts}),g("ul",{className:"space-y-1 text-sm",children:e.submissions.map(s=>E("li",{className:"flex items-center gap-2 text-neutral-700 dark:text-neutral-300",children:[s.passed?g(Ft,{className:"size-4 text-emerald-600","aria-hidden":!0}):g(_t,{className:"size-4 text-red-600","aria-hidden":!0}),I(t.testAttemptRow,{percent:s.scorePercent,date:s.createdAt?.toLocaleString()??""})]},s.id))})]})}function Ds({test:e,onSubmitted:t,onTryAgain:s}){let{dataSource:o,texts:n}=C(),[a,r]=Se({}),[i,u]=Se(),[b,A]=Se(!1),[V,l]=Se(),p=Yt(Date.now()),f=Yt(e.questions).current,d=e.attemptsLeft!=null&&e.attemptsLeft<=0,N=i?i.attemptsLeft:e.attemptsLeft,v=At(f,a),x=c=>a[c.id]??xt(c.config);async function W(){A(!0),l(void 0);try{let c={};for(let P of f)c[P.id]=x(P);let h=await o.submitTest(e.id,c,Date.now()-p.current);u(h),t(h)}catch(c){l(c)}finally{A(!1)}}let L=new Map(i?.results.map(c=>[c.questionId,c]));return f.length===0?g("p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:n.testNoQuestions}):E("form",{className:"space-y-6",onSubmit:c=>{c.preventDefault(),i==null&&!d&&W()},children:[i&&E("div",{role:"status",className:y("rounded-xl p-4 text-sm",i.submission.passed?"bg-emerald-50 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300":"bg-red-50 text-red-800 dark:bg-red-950 dark:text-red-300"),children:[g("p",{className:"text-base font-semibold",children:i.submission.passed?n.testPassed:n.testFailed}),g("p",{children:I(n.testScore,{percent:i.submission.scorePercent})})]}),g("ol",{className:"space-y-6",children:f.map((c,h)=>{let P=L.get(c.id),Z=P?.correctAnswer!=null?St(c,P.correctAnswer):"";return E("li",{className:"space-y-3","data-question-id":c.id,children:[E("p",{className:"font-medium break-words",children:[h+1,". ",c.prompt]}),g(js,{question:c,answer:x(c),disabled:i!=null||b||d,onChange:k=>r(X=>({...X,[c.id]:k}))}),P&&E("div",{className:y("flex items-start gap-2 text-sm",P.correct?"text-emerald-700 dark:text-emerald-400":"text-red-700 dark:text-red-400"),children:[P.correct?g(Ft,{className:"mt-0.5 size-4 shrink-0","aria-hidden":!0}):g(_t,{className:"mt-0.5 size-4 shrink-0","aria-hidden":!0}),E("span",{children:[P.correct?n.testCorrect:n.testIncorrect,Z!==""&&!P.correct&&E("span",{className:"block text-neutral-700 dark:text-neutral-300",children:[n.testCorrectAnswer,": ",Z]})]})]})]},c.id)})}),V!=null&&g("p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:q(V,n)}),i==null?E("div",{className:"space-y-2",children:[v>0&&!d&&g("p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:I(n.testUnanswered,{count:v})}),g(T,{type:"submit",className:H,disabled:b||d,children:b?n.testSubmitting:n.testSubmit})]}):N!==0&&!i.submission.passed&&g(T,{type:"button",variant:"outline",onClick:s,children:n.testTryAgain})]})}function Kt({testId:e,title:t,onSubmitted:s}){let{dataSource:o,texts:n}=C(),a=F(),r=O(()=>o.getTest(e),[o,e]),i=O(()=>o.listSubmissions(e).catch(()=>{}),[o,e]),[u,b]=Se(0);return E("article",{"data-test-id":e,className:y(z,"min-w-0 space-y-5"),children:[g("h2",{className:"text-2xl font-semibold break-words",children:r.status==="success"?r.data.title:t}),r.status==="loading"&&g(Ae,{}),r.status==="error"&&g(oe,{error:r.error,onRetry:r.reload}),r.status==="success"&&E(Hs,{children:[r.data.description.trim()!==""&&g("p",{className:"text-sm break-words whitespace-pre-wrap text-neutral-700 dark:text-neutral-300",children:r.data.description}),E("ul",{className:"space-y-1 text-sm text-neutral-600 dark:text-neutral-400",children:[r.data.gating&&g("li",{children:n.testGating}),g("li",{children:I(n.testPassThreshold,{percent:r.data.passThresholdPercent})}),g("li",{children:r.data.attemptsLeft==null?n.testAttemptsUnlimited:I(n.testAttemptsLeft,{count:r.data.attemptsLeft})}),r.data.attemptCount>0&&g("li",{children:I(n.testBestScore,{percent:r.data.bestScorePercent})})]}),r.data.attemptsLeft!=null&&r.data.attemptsLeft<=0&&g("p",{role:"alert",className:"text-sm font-medium text-red-700 dark:text-red-400",children:n.testNoAttemptsLeft}),g(Ds,{test:r.data,onSubmitted:A=>{a("test_submitted",{lesson_id:e,score_bucket:Oe(A.submission.scorePercent)}),s(A),i.refresh(),r.refresh()},onTryAgain:()=>b(A=>A+1)},u),i.status==="success"&&i.data&&g(Js,{list:i.data})]})]})}import{jsx as S,jsxs as U}from"react/jsx-runtime";var jt="bitsnap-course-autoplay";function tn(){let[e,t]=Te(!1);je(()=>{try{t(window.localStorage.getItem(jt)==="1")}catch{}},[]);let s=Jt(o=>{t(o);try{window.localStorage.setItem(jt,o?"1":"0")}catch{}},[]);return[e,s]}function _e(e){return e==null?void 0:{id:e.id,kind:e.kind}}function sn({item:e}){let{texts:t}=C();return U("div",{className:y(z,"space-y-2"),role:"status",children:[S("h2",{className:"text-2xl font-semibold break-words",children:e.title}),S("p",{className:"text-sm text-neutral-600 dark:text-neutral-300",children:e.lockReason?I(t.lockedPage,{test:e.lockReason.gatingTestTitle}):t.locked})]})}function nn({course:e,item:t,autoplay:s,onAutoplayChange:o,startPlaying:n,onNavigate:a,onProgressSaved:r,onEnded:i,onLocked:u,mobileOutline:b}){let{dataSource:A,texts:V}=C(),l=O(()=>A.getLesson(t.id),[A,t.id]),p=l.status==="error"&&l.error instanceof B&&l.error.code==="item_locked";if(je(()=>{p&&u()},[p]),l.status==="loading")return S(Ae,{});if(l.status==="error")return p?U("div",{className:y(z,"space-y-2"),role:"alert",children:[S("h2",{className:"text-2xl font-semibold break-words",children:t.title}),S("p",{className:"text-sm",children:q(l.error,V)})]}):S(oe,{error:l.error,onRetry:l.reload});let f=Ee(e,t.id),d=xe(e).filter(N=>N.kind!=="test").findIndex(N=>N.id===t.id)+1;return S(Qt,{lesson:l.data,position:d,mobileOutline:b,nextItem:le(e,(l.data.next??_e(f.next))?.id),courseType:e.type,prev:l.data.prev??_e(f.prev),next:l.data.next??_e(f.next),autoplay:s,onAutoplayChange:o,startPlaying:n,onNavigate:a,onProgressSaved:r,onEnded:i,onUrlsExpired:l.refresh},l.data.id)}function on({course:e,selectedId:t,onSelect:s,onBack:o,onCourseChange:n,onCourseRefresh:a}){let{texts:r,preview:i}=C(),u=F(),b=en(null),A=$s(),[V,l]=Te(!1),[p,f]=tn(),[d,N]=Te(),v=mt(e,t),x=le(e,v);je(()=>{u("course_opened",{})},[e.id]);let W=(k,X=!1)=>{N(X?k:void 0),l(!1),s(k),b.current?.scrollIntoView({block:"start",behavior:"smooth"})},L=S(Ct,{course:e,selectedId:v,variant:e.type==="video"?"playlist":"modules",onSelect:k=>W(k.id)}),c;x==null?c=S("p",{className:"text-sm text-neutral-500",children:r.noItem}):x.locked?c=S(sn,{item:x}):x.kind==="test"?c=S(Kt,{testId:x.id,title:x.title,onSubmitted:()=>a()},x.id):c=S(nn,{course:e,item:x,autoplay:p,onAutoplayChange:f,startPlaying:d===x.id,onNavigate:k=>W(k.id),onProgressSaved:(k,X)=>n(te=>({...X.completed?pt(te,k):te,completedLessons:X.completedLessons,progressPercent:X.progressPercent})),onEnded:()=>{if(!p)return;let k=dt(e,x.id);k&&W(k.id,!0)},onLocked:a,mobileOutline:L},x.id);let h=e.type==="video",P=!h||x==null||x.locked||x.kind==="test",Z=e.resumeItemId!==""&&e.resumeItemId!==v&&le(e,e.resumeItemId)?.locked===!1;return U("div",{ref:b,className:"space-y-4 scroll-mt-4",children:[U("header",{className:"space-y-3",children:[U("div",{className:"flex flex-wrap items-center gap-2",children:[o&&U(T,{variant:"ghost",size:"sm",onClick:o,children:[S(Ke,{"aria-hidden":!0}),r.back]}),i&&S("span",{"data-course-preview":!0,className:"rounded-full bg-amber-100 px-3 py-0.5 text-xs font-semibold text-amber-900 dark:bg-amber-900/40 dark:text-amber-200",children:r.previewBadge})]}),U("div",{className:"flex flex-wrap items-end justify-between gap-4",children:[S("div",{className:"min-w-0 space-y-1",children:S("h1",{className:"text-2xl font-semibold break-words sm:text-3xl",children:e.title})}),Z&&U(T,{size:"sm",variant:"outline",onClick:()=>W(e.resumeItemId),children:[S(Os,{"aria-hidden":!0}),r.continueCourse]})]}),U("div",{className:"max-w-xl space-y-1 md:max-w-none",children:[e.progressPercent>=100&&S("p",{role:"status",className:"font-semibold text-emerald-700 dark:text-emerald-400",children:r.courseCompleted??r.completed}),S(It,{percent:e.progressPercent,label:I(r.progressPercent,{percent:e.progressPercent})}),U("p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:[I(r.progressLessons,{done:e.completedLessons,total:e.totalLessons})," \xB7 ",I(r.progressPercent,{percent:e.progressPercent})]})]})]}),U("div",{className:y("grid gap-6",h?"lg:grid-cols-[minmax(0,1fr)_380px]":"lg:grid-cols-[320px_minmax(0,1fr)]"),children:[P&&S("div",{className:"lg:hidden",children:U(T,{variant:"outline",size:"sm",className:"min-h-11","aria-controls":A,"aria-expanded":V,onClick:()=>l(k=>!k),children:[S(qs,{"aria-hidden":!0}),r.contents]})}),P&&S("aside",{id:A,className:y(z,h&&"lg:hidden","p-3 sm:p-3 lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto",V?"block":"hidden",!h&&"lg:block"),children:L}),S("div",{className:"min-w-0","data-course-content":!0,children:c}),h&&S("aside",{className:y(z,"hidden p-3 sm:p-3 lg:block lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto"),children:L})]})]})}function rn({accessId:e,accessToken:t,dataSource:s,preview:o=!1,itemId:n,onItemChange:a,onBack:r,initialCourse:i,texts:u,locale:b,theme:A,className:V,analytics:l}){let p=at(),f=qe(p.projectID,o?!1:l,"course"),d=Fe(()=>Ye(u,b),[u,b]),N=Fe(()=>{if(s)return s;if(e==null||e==="")throw new Error("PanelCourse needs an accessId or a dataSource");return Be({accessId:e,accessToken:t})},[s,e,t]),v=O(()=>N.getCourse(),[N],i),[x,W]=Te(),L=a?n:n??x,c=Jt(k=>{W(k),a?.(k)},[a]),h=lt(A?.colors??p.theme?.colors),P=v.status==="success"?v.data.id:void 0,Z=Fe(()=>({dataSource:N,texts:d,preview:o,analytics:{courseId:P,instrument:f}}),[N,d,o,P,f]);return S(He,{value:f,children:S(ut,{value:Z,children:U("div",{"data-bitsnap-course":!0,"data-course-type":v.status==="success"?v.data.type:void 0,style:h,className:y("font-inter w-full max-w-7xl",V),children:[v.status==="loading"&&U("div",{className:"space-y-4",children:[r&&U(T,{variant:"ghost",size:"sm",onClick:r,children:[S(Ke,{"aria-hidden":!0}),d.back]}),S("p",{className:"sr-only",role:"status",children:d.loading}),S(Ae,{})]}),v.status==="error"&&U("div",{className:"space-y-4",children:[r&&U(T,{variant:"ghost",size:"sm",onClick:r,children:[S(Ke,{"aria-hidden":!0}),d.back]}),S(oe,{error:v.error,onRetry:v.reload})]}),v.status==="success"&&S(on,{course:v.data,selectedId:L,onSelect:c,onBack:r,onCourseChange:v.mutate,onCourseRefresh:v.refresh})]})})})}export{Wt as COURSE_TEXT_TABLES,B as CourseError,rn as PanelCourse,Le as createCourseDataSource,Be as createRpcCourseDataSource,Ye as getCourseTexts,ze as sanitizeLessonHtml};
|
|
2
2
|
//# sourceMappingURL=course.mjs.map
|