mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
multi line paste for add course and edit course
This commit is contained in:
@@ -131,7 +131,7 @@ export function Hero({ ads, onCtaClick }) {
|
|||||||
|
|
||||||
{/* Navigation and Progress — only meaningful with more than one slide */}
|
{/* Navigation and Progress — only meaningful with more than one slide */}
|
||||||
{ads.length > 1 && (
|
{ads.length > 1 && (
|
||||||
<div className="flex items-center justify-between mt-4 px-2">
|
<div className="flex items-center justify-between mt-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CarouselPrevious className="static translate-y-0 size-8 rounded-lg" />
|
<CarouselPrevious className="static translate-y-0 size-8 rounded-lg" />
|
||||||
<CarouselNext className="static translate-y-0 size-8 rounded-lg" />
|
<CarouselNext className="static translate-y-0 size-8 rounded-lg" />
|
||||||
|
|||||||
@@ -516,6 +516,7 @@ export function VideoBlock({ content, onWatchProgress, resumePercent, antiSkipEn
|
|||||||
playsInline
|
playsInline
|
||||||
controlsList="nodownload nofullscreen noremoteplayback"
|
controlsList="nodownload nofullscreen noremoteplayback"
|
||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
|
disableRemotePlayback
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
className={`w-full h-full ${isFullscreen ? "object-contain" : "object-cover"}`}
|
className={`w-full h-full ${isFullscreen ? "object-contain" : "object-cover"}`}
|
||||||
/>
|
/>
|
||||||
@@ -610,7 +611,7 @@ export function VideoBlock({ content, onWatchProgress, resumePercent, antiSkipEn
|
|||||||
style={{ left: `${hoverProgress.x}px`, transform: "translateX(-50%)" }}
|
style={{ left: `${hoverProgress.x}px`, transform: "translateX(-50%)" }}
|
||||||
>
|
>
|
||||||
<div className="rounded-md overflow-hidden border border-white/20 shadow-xl" style={{ width: 160, height: 90 }}>
|
<div className="rounded-md overflow-hidden border border-white/20 shadow-xl" style={{ width: 160, height: 90 }}>
|
||||||
<video ref={previewVidRef} src={blobUrl} preload="auto" muted playsInline disablePictureInPicture onContextMenu={(e) => e.preventDefault()} className="w-full h-full object-cover" />
|
<video ref={previewVidRef} src={blobUrl} preload="auto" muted playsInline disablePictureInPicture disableRemotePlayback onContextMenu={(e) => e.preventDefault()} className="w-full h-full object-cover" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-white text-xs mt-1 font-medium tabular-nums drop-shadow">{fmtTime(hoverProgress.time)}</span>
|
<span className="text-white text-xs mt-1 font-medium tabular-nums drop-shadow">{fmtTime(hoverProgress.time)}</span>
|
||||||
<div className="w-2 h-2 bg-black/60 rotate-45 -mt-1 border-r border-b border-white/20" />
|
<div className="w-2 h-2 bg-black/60 rotate-45 -mt-1 border-r border-b border-white/20" />
|
||||||
|
|||||||
@@ -181,8 +181,19 @@ export default function AddCourse() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { fields: objectiveFields, append: appendObjective, remove: removeObjective } =
|
const { fields: objectiveFields, append: appendObjective, remove: removeObjective, insert: insertObjective } =
|
||||||
useFieldArray({ control, name: "objectives" });
|
useFieldArray({ control, name: "objectives" });
|
||||||
|
|
||||||
|
const handleObjectivePaste = (e, index) => {
|
||||||
|
const text = e.clipboardData.getData("text");
|
||||||
|
const lines = text.split(/\r\n|\r|\n/).map((l) => l.trim()).filter(Boolean);
|
||||||
|
if (lines.length <= 1) return;
|
||||||
|
e.preventDefault();
|
||||||
|
setValue(`objectives.${index}.text`, lines[0], { shouldDirty: true, shouldValidate: true });
|
||||||
|
lines.slice(1).forEach((line, i) => {
|
||||||
|
insertObjective(index + 1 + i, { text: line });
|
||||||
|
});
|
||||||
|
};
|
||||||
const { fields: roleFields, append: appendRole, remove: removeRole } =
|
const { fields: roleFields, append: appendRole, remove: removeRole } =
|
||||||
useFieldArray({ control, name: "roles" });
|
useFieldArray({ control, name: "roles" });
|
||||||
|
|
||||||
@@ -415,6 +426,7 @@ export default function AddCourse() {
|
|||||||
<Input
|
<Input
|
||||||
placeholder={`Objective ${index + 1}`}
|
placeholder={`Objective ${index + 1}`}
|
||||||
{...register(`objectives.${index}.text`)}
|
{...register(`objectives.${index}.text`)}
|
||||||
|
onPaste={(e) => handleObjectivePaste(e, index)}
|
||||||
/>
|
/>
|
||||||
<FieldError message={errors.objectives?.[index]?.text?.message} />
|
<FieldError message={errors.objectives?.[index]?.text?.message} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -239,8 +239,19 @@ export default function EditCourse() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { fields: objectiveFields, append: appendObjective, remove: removeObjective } =
|
const { fields: objectiveFields, append: appendObjective, remove: removeObjective, insert: insertObjective } =
|
||||||
useFieldArray({ control, name: "objectives" });
|
useFieldArray({ control, name: "objectives" });
|
||||||
|
|
||||||
|
const handleObjectivePaste = (e, index) => {
|
||||||
|
const text = e.clipboardData.getData("text");
|
||||||
|
const lines = text.split(/\r\n|\r|\n/).map((l) => l.trim()).filter(Boolean);
|
||||||
|
if (lines.length <= 1) return;
|
||||||
|
e.preventDefault();
|
||||||
|
setValue(`objectives.${index}.text`, lines[0], { shouldDirty: true, shouldValidate: true });
|
||||||
|
lines.slice(1).forEach((line, i) => {
|
||||||
|
insertObjective(index + 1 + i, { text: line });
|
||||||
|
});
|
||||||
|
};
|
||||||
const { fields: roleFields, append: appendRole, remove: removeRole } =
|
const { fields: roleFields, append: appendRole, remove: removeRole } =
|
||||||
useFieldArray({ control, name: "roles" });
|
useFieldArray({ control, name: "roles" });
|
||||||
|
|
||||||
@@ -675,6 +686,7 @@ export default function EditCourse() {
|
|||||||
<Input
|
<Input
|
||||||
placeholder={`Objective ${index + 1}`}
|
placeholder={`Objective ${index + 1}`}
|
||||||
{...register(`objectives.${index}.text`)}
|
{...register(`objectives.${index}.text`)}
|
||||||
|
onPaste={(e) => handleObjectivePaste(e, index)}
|
||||||
/>
|
/>
|
||||||
<FieldError message={errors.objectives?.[index]?.text?.message} />
|
<FieldError message={errors.objectives?.[index]?.text?.message} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -704,7 +704,7 @@ const CourseDetails = () => {
|
|||||||
<span>{progressSummary.lessons_completed} of {progressSummary.lessons_total} lessons complete</span>
|
<span>{progressSummary.lessons_completed} of {progressSummary.lessons_total} lessons complete</span>
|
||||||
<span>{progressSummary.percent}%</span>
|
<span>{progressSummary.percent}%</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={progressSummary.percent} />
|
<Progress value={progressSummary.percent} className="[&>div]:bg-blue-500" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div ref={CourseBreadcrumb} className="w-fit">
|
<div ref={CourseBreadcrumb} className="w-fit">
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ const LessonDetails = () => {
|
|||||||
className="flex-1 flex flex-col"
|
className="flex-1 flex flex-col"
|
||||||
style={{ paddingTop: "var(--navbar-h)" }}
|
style={{ paddingTop: "var(--navbar-h)" }}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-6 flex-1 min-w-0 xs:px-4 xs:py-8 lg:px-16 lg:py-10">
|
<div className="flex flex-col gap-6 lg:container lg:max-w-4xl mx-auto xs:px-4 xs:py-8 lg:px-16 lg:py-10">
|
||||||
<AppBreadcrumb items={items} />
|
<AppBreadcrumb items={items} />
|
||||||
|
|
||||||
{taskCtx?.has_task && (
|
{taskCtx?.has_task && (
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ const ProfilePage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-17 bg-muted h-full">
|
<div className="mt-17 bg-muted min-h-screen">
|
||||||
<div className="p-6 lg:container lg:max-w-5xl lg:mx-auto space-y-4">
|
<div className="p-6 lg:container lg:max-w-5xl lg:mx-auto space-y-4">
|
||||||
|
|
||||||
{/* ── Header card ──────────────────────────────────────────────────── */}
|
{/* ── Header card ──────────────────────────────────────────────────── */}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ const UnitDetails = () => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageMeta title={unitDetail ? `${unitDetail.title} - STARR` : undefined} description={unitDetail?.description} />
|
<PageMeta title={unitDetail ? `${unitDetail.title} - STARR` : undefined} description={unitDetail?.description} />
|
||||||
<div className="xs:my-20 lg:my-28 lg:container lg:mx-auto xs:px-4 lg:px-4 flex flex-col gap-6 max-w-3xl">
|
<div className="xs:py-24 xs:px-8 lg:px-0 lg:py-28 flex flex-col gap-6 lg:container lg:mx-auto lg:max-w-2xl">
|
||||||
<AppBreadcrumb items={items} />
|
<AppBreadcrumb items={items} />
|
||||||
|
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ function LandingPage() {
|
|||||||
{ContactData.map(({ id, icon: Icon, label, value }) => (
|
{ContactData.map(({ id, icon: Icon, label, value }) => (
|
||||||
<div
|
<div
|
||||||
key={id}
|
key={id}
|
||||||
className="flex items-center justify-between bg-primary dark:bg-blue-600 rounded-md px-4 py-2 text-sm text-primary dark:text-white"
|
className="flex items-center justify-between bg-primary dark:bg-blue-600 rounded-md px-4 py-2 text-sm text-primary-foreground dark:text-white"
|
||||||
>
|
>
|
||||||
<div className="flex items-center flex-wrap gap-2">
|
<div className="flex items-center flex-wrap gap-2">
|
||||||
<Icon className="size-4" />
|
<Icon className="size-4" />
|
||||||
|
|||||||
Reference in New Issue
Block a user