This commit is contained in:
ditiqi 2025-03-27 20:47:44 +08:00
commit 17633bcbd3
23 changed files with 314 additions and 204 deletions

View File

@ -1,5 +1,5 @@
# 基础镜像
FROM node:18.17-alpine as base
FROM node:18-alpine as base
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
echo "https://mirrors.aliyun.com/alpine/v3.18/community" >> /etc/apk/repositories

View File

@ -125,5 +125,14 @@ export class PostRouter {
const { staff } = ctx;
return await this.postService.updateOrderByIds(input.ids);
}),
softDeletePostDescendant:this.trpc.protectProcedure
.input(
z.object({
ancestorId:z.string()
})
)
.mutation(async ({ input })=>{
return await this.postService.softDeletePostDescendant(input)
})
});
}

View File

@ -101,6 +101,7 @@ export class PostService extends BaseTreeService<Prisma.PostDelegate> {
},
params: { staff?: UserProfile; tx?: Prisma.TransactionClient },
) {
const { courseDetail } = args;
// If no transaction is provided, create a new one
if (!params.tx) {
@ -295,40 +296,33 @@ export class PostService extends BaseTreeService<Prisma.PostDelegate> {
staff?.id && {
authorId: staff.id,
},
// staff?.id && {
// watchableStaffs: {
// some: {
// id: staff.id,
// },
// },
// },
// deptId && {
// watchableDepts: {
// some: {
// id: {
// in: parentDeptIds,
// },
// },
// },
// },
// {
// AND: [
// {
// watchableStaffs: {
// none: {}, // 匹配 watchableStaffs 为空
// },
// },
// {
// watchableDepts: {
// none: {}, // 匹配 watchableDepts 为空
// },
// },
// ],
// },
].filter(Boolean);
if (orCondition?.length > 0) return orCondition;
return undefined;
}
async softDeletePostDescendant(args:{ancestorId?:string}){
const { ancestorId } = args
const descendantIds = []
await db.postAncestry.findMany({
where:{
ancestorId,
},
select:{
descendantId:true
}
}).then(res=>{
res.forEach(item=>{
descendantIds.push(item.descendantId)
})
})
console.log(descendantIds)
const result = super.softDeleteByIds([...descendantIds,ancestorId])
EventBus.emit('dataChanged', {
type: ObjectType.POST,
operation: CrudOperation.DELETED,
data: result,
});
return result
}
}

View File

@ -64,6 +64,7 @@
"react-dom": "18.2.0",
"react-hook-form": "^7.54.2",
"react-hot-toast": "^2.4.1",
"react-player": "^2.16.0",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.24.1",
"superjson": "^2.2.1",

View File

@ -6,7 +6,7 @@ export default function PathEditorPage() {
const { id } = useParams();
return (
<PostDetailProvider editId={id}>
<MindEditor id={id}></MindEditor>;
<MindEditor id={id}></MindEditor>
</PostDetailProvider>
);
}

View File

@ -25,6 +25,8 @@ import JoinButton from "../../models/course/detail/CourseOperationBtns/JoinButto
import { CourseDetailContext } from "../../models/course/detail/PostDetailContext";
import ReactDOM from "react-dom";
import { createRoot } from "react-dom/client";
import { useQueryClient } from "@tanstack/react-query";
import { getQueryKey } from "@trpc/react-query";
export default function MindEditor({ id }: { id?: string }) {
const containerRef = useRef<HTMLDivElement>(null);
const {
@ -36,6 +38,7 @@ export default function MindEditor({ id }: { id?: string }) {
const [instance, setInstance] = useState<MindElixirInstance | null>(null);
const { isAuthenticated, user, hasSomePermissions } = useAuth();
const { read } = useVisitor();
const queryClient = useQueryClient();
// const { data: post, isLoading }: { data: PathDto; isLoading: boolean } =
// api.post.findFirst.useQuery(
// {
@ -46,7 +49,11 @@ export default function MindEditor({ id }: { id?: string }) {
// },
// { enabled: Boolean(id) }
// );
const softDeletePostDescendant = api.post.softDeletePostDescendant.useMutation({
onSuccess:()=>{
queryClient.invalidateQueries({ queryKey: getQueryKey(api.post) });
}
})
const canEdit: boolean = useMemo(() => {
const isAuth = isAuthenticated && user?.id === post?.author?.id;
return (
@ -62,16 +69,16 @@ export default function MindEditor({ id }: { id?: string }) {
});
const { handleFileUpload } = useTusUpload();
const [form] = Form.useForm();
const CustomLinkIconPlugin = (mind) => {
mind.bus.addListener("operation", async () => {
const hyperLinkElement =
await document.querySelectorAll(".hyper-link");
const handleIcon = () => {
const hyperLinkElement = document.querySelectorAll(".hyper-link");
console.log("hyperLinkElement", hyperLinkElement);
hyperLinkElement.forEach((item) => {
const hyperLinkDom = createRoot(item);
hyperLinkDom.render(<LinkOutlined />);
});
});
}
const CustomLinkIconPlugin = (mind) => {
mind.bus.addListener("operation", handleIcon)
};
useEffect(() => {
if (post?.id && id) {
@ -133,7 +140,11 @@ export default function MindEditor({ id }: { id?: string }) {
containerRef.current.hidden = true;
//挂载实例
setInstance(mind);
}, [canEdit]);
useEffect(() => {
handleIcon()
});
useEffect(() => {
if ((!id || post) && instance) {
containerRef.current.hidden = false;
@ -204,10 +215,16 @@ export default function MindEditor({ id }: { id?: string }) {
}
console.log(result);
},
(error) => {},
(error) => { },
`mind-thumb-${new Date().toString()}`
);
};
const handleDelete = async () => {
await softDeletePostDescendant.mutateAsync({
ancestorId: id,
});
navigate("/path");
}
useEffect(() => {
containerRef.current.style.height = `${Math.floor(window.innerHeight - 271)}px`;
}, []);
@ -248,7 +265,20 @@ export default function MindEditor({ id }: { id?: string }) {
</div>
<div>
{canEdit && (
<>
{
id && (
<Button
danger
icon={<SaveOutlined></SaveOutlined>}
onSubmit={(e) => e.preventDefault()}
onClick={handleDelete}>
</Button>
)
}
<Button
className="ml-4"
ghost
type="primary"
icon={<SaveOutlined></SaveOutlined>}
@ -256,6 +286,7 @@ export default function MindEditor({ id }: { id?: string }) {
onClick={handleSave}>
{id ? "更新" : "保存"}
</Button>
</>
)}
</div>
</div>

View File

@ -12,7 +12,7 @@ import PostSelect from "../../models/post/PostSelect/PostSelect";
import { Lecture, PostType } from "@nice/common";
import { xmindColorPresets } from "./constant";
import { api } from "@nice/client";
import { env } from "@web/src/env";
import { useAuth } from "@web/src/providers/auth-provider";
interface NodeMenuProps {
mind: MindElixirInstance;
@ -20,12 +20,13 @@ interface NodeMenuProps {
//管理节点样式状态
const NodeMenu: React.FC<NodeMenuProps> = ({ mind }) => {
const [isOpen, setIsOpen] = useState(false);
const [selectedFontColor, setSelectedFontColor] = useState<string>("");
const [selectedBgColor, setSelectedBgColor] = useState<string>("");
const [selectedSize, setSelectedSize] = useState<string>("");
const [isBold, setIsBold] = useState(false);
const { user} = useAuth();
const [urlMode, setUrlMode] = useState<"URL" | "POSTURL">("POSTURL");
const [url, setUrl] = useState<string>("");
const [postId, setPostId] = useState<string>("");
@ -238,13 +239,15 @@ const NodeMenu: React.FC<NodeMenuProps> = ({ mind }) => {
{urlMode === "POSTURL" ? (
<PostSelect
onChange={(value) => {
if (typeof value === "string") {
if (typeof value === "string" ) {
setPostId(value);
}
}}
params={{
where: {
type: PostType.LECTURE,
deletedAt: null,
authorId: user?.id,
},
}}
/>

View File

@ -7,8 +7,10 @@ import { formatFileSize, getCompressedImageUrl } from "@nice/utils";
export default function ResourcesShower({
resources = [],
isShowImage
}: {
resources: ResourceDto[];
isShowImage?: boolean;
}) {
const { resources: dealedResources } = useMemo(() => {
if (!resources) return { resources: [] };
@ -36,7 +38,7 @@ export default function ResourcesShower({
const fileResources = dealedResources.filter((res) => !res.isImage);
return (
<div className="space-y-3">
{imageResources.length > 0 && (
{imageResources.length > 0 && isShowImage && (
<Row gutter={[16, 16]} className="mb-6">
<Image.PreviewGroup>
{imageResources.map((resource) => (

View File

@ -1,4 +1,4 @@
import { useCallback, useState } from "react";
import { ReactNode, useCallback, useState } from "react";
import {
UploadOutlined,
CheckCircleOutlined,
@ -12,6 +12,9 @@ export interface TusUploaderProps {
onChange?: (value: string[]) => void;
multiple?: boolean;
allowTypes?: string[];
style?:string
icon?:ReactNode,
description?:string
}
interface UploadingFile {
@ -27,6 +30,9 @@ export const TusUploader = ({
onChange,
multiple = true,
allowTypes = undefined,
style="",
icon = <UploadOutlined />,
description = "点击或拖拽文件到此区域进行上传",
}: TusUploaderProps) => {
const { handleFileUpload, uploadProgress } = useTusUpload();
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]);
@ -137,7 +143,7 @@ export const TusUploader = ({
);
return (
<div className="space-y-1">
<div className={`space-y-1 ${style}`}>
<Upload.Dragger
accept={allowTypes?.join(",")}
name="files"
@ -145,18 +151,18 @@ export const TusUploader = ({
showUploadList={false}
beforeUpload={handleBeforeUpload}>
<p className="ant-upload-drag-icon">
<UploadOutlined />
{icon}
</p>
<p className="ant-upload-text">
{description}
</p>
<p className="ant-upload-hint">
{multiple ? "支持单个或批量上传文件" : "仅支持上传单个文件"}
{allowTypes && (
{/* {allowTypes && (
<span className="block text-xs text-gray-500">
: {allowTypes.join(", ")}
</span>
)}
)} */}
</p>
<div className="px-2 py-0 rounded mt-1">

View File

@ -10,7 +10,7 @@ import { Skeleton } from "antd";
import ResourcesShower from "@web/src/components/common/uploader/ResourceShower";
import { useNavigate } from "react-router-dom";
import CourseDetailTitle from "./CourseDetailTitle";
import ReactPlayer from "react-player";
export const CourseDetailDisplayArea: React.FC = () => {
// 创建滚动动画效果
const {
@ -41,6 +41,15 @@ export const CourseDetailDisplayArea: React.FC = () => {
}}
className="w-full bg-black rounded-lg ">
<div className=" w-full cursor-pointer">
{/* <ReactPlayer
url={lecture?.meta?.videoUrl}
controls={true}
width="100%"
height="100%"
onError={(error) => {
console.log(error);
}}
/> */}
<VideoPlayer src={lecture?.meta?.videoUrl} />
</div>
</motion.div>
@ -48,18 +57,22 @@ export const CourseDetailDisplayArea: React.FC = () => {
)}
{!lectureIsLoading &&
selectedLectureId &&
lecture?.meta?.type === LectureType.ARTICLE && (
(
<div className="flex justify-center flex-col items-center gap-2 w-full my-2 ">
<div className="w-full rounded-lg ">
{lecture?.meta?.type === LectureType.ARTICLE && (
<CollapsibleContent
content={lecture?.content || ""}
maxHeight={500} // Optional, defaults to 150
/>
)}
<div className="px-6">
<ResourcesShower
resources={
lecture?.resources
}></ResourcesShower>
}
isShowImage = {lecture?.meta?.type === LectureType.ARTICLE}
></ResourcesShower>
</div>
</div>
</div>

View File

@ -23,15 +23,9 @@ export default function CourseDetailTitle() {
{!selectedLectureId ? course?.title : lecture?.title}
</div>
<div className="text-gray-600 flex w-full justify-start items-center gap-5">
{course?.author?.showname && (
<div>
:
{course?.author?.showname}
</div>
)}
{course?.depts && course?.depts?.length > 0 && (
<div>
:
{course?.depts?.map((dept) => dept.name)}
</div>
)}

View File

@ -1,86 +1,26 @@
import { useAuth } from "@web/src/providers/auth-provider";
import { useContext, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { CourseDetailContext } from "../PostDetailContext";
import { useStaff } from "@nice/client";
import { api } from "@nice/client";
import {
CheckCircleOutlined,
CloseCircleOutlined,
DeleteTwoTone,
EditTwoTone,
LoginOutlined,
ExclamationCircleFilled,
} from "@ant-design/icons";
import toast from "react-hot-toast";
import JoinButton from "./JoinButton";
import { Modal } from "antd";
import { useQueryClient } from "@tanstack/react-query";
import { getQueryKey } from "@trpc/react-query";
export default function CourseOperationBtns() {
// const { isAuthenticated, user } = useAuth();
const navigate = useNavigate();
const { post, canEdit, userIsLearning, setUserIsLearning } =
useContext(CourseDetailContext);
// const { update } = useStaff();
// const [isHovered, setIsHovered] = useState(false);
// const toggleLearning = async () => {
// if (!userIsLearning) {
// await update.mutateAsync({
// where: { id: user?.id },
// data: {
// learningPosts: {
// connect: { id: course.id },
// },
// },
// });
// setUserIsLearning(true);
// toast.success("加入学习成功");
// } else {
// await update.mutateAsync({
// where: { id: user?.id },
// data: {
// learningPosts: {
// disconnect: {
// id: course.id,
// },
// },
// },
// });
// toast.success("退出学习成功");
// setUserIsLearning(false);
// }
// };
const { post, canEdit } = useContext(CourseDetailContext);
return (
<>
{/* {isAuthenticated && (
<div
onClick={toggleLearning}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={`flex px-1 py-0.5 gap-1 hover:cursor-pointer transition-all ${
userIsLearning
? isHovered
? "text-red-500 border-red-500 rounded-md "
: "text-green-500 "
: "text-primary "
}`}>
{userIsLearning ? (
isHovered ? (
<CloseCircleOutlined />
) : (
<CheckCircleOutlined />
)
) : (
<LoginOutlined />
)}
<span>
{userIsLearning
? isHovered
? "退出学习"
: "正在学习"
: "加入学习"}
</span>
</div>
)} */}
<JoinButton></JoinButton>
{canEdit && (
<>
<div
className="flex gap-1 px-1 py-0.5 text-primary hover:cursor-pointer"
onClick={() => {
@ -92,6 +32,9 @@ export default function CourseOperationBtns() {
<EditTwoTone></EditTwoTone>
{"编辑课程"}
</div>
</>
)}
</>
);

View File

@ -13,6 +13,8 @@ import { api, usePost } from "@nice/client";
import { useNavigate } from "react-router-dom";
import { z } from "zod";
import { useAuth } from "@web/src/providers/auth-provider";
import { getQueryKey } from "@trpc/react-query";
import { useQueryClient } from "@tanstack/react-query";
export type CourseFormData = {
title: string;
@ -26,6 +28,7 @@ export type CourseFormData = {
interface CourseEditorContextType {
onSubmit: (values: CourseFormData) => Promise<void>;
handleDeleteCourse: () => Promise<void>;
editId?: string;
course?: CourseDto;
taxonomies?: Taxonomy[]; // 根据实际类型调整
@ -45,6 +48,7 @@ export function CourseFormProvider({
}: CourseFormProviderProps) {
const [form] = Form.useForm<CourseFormData>();
const { create, update, createCourse } = usePost();
const queryClient = useQueryClient();
const { user } = useAuth();
const { data: course }: { data: CourseDto } = api.post.findFirst.useQuery(
{
@ -60,6 +64,11 @@ export function CourseFormProvider({
} = api.taxonomy.getAll.useQuery({
type: ObjectType.COURSE,
});
const softDeletePostDescendant = api.post.softDeletePostDescendant.useMutation({
onSuccess:()=>{
queryClient.invalidateQueries({ queryKey: getQueryKey(api.post) });
}
})
const navigate = useNavigate();
useEffect(() => {
@ -92,7 +101,15 @@ export function CourseFormProvider({
form.setFieldsValue(formData);
}
}, [course, form]);
const handleDeleteCourse = async () => {
if(editId){
await softDeletePostDescendant.mutateAsync({
ancestorId: editId,
});
navigate("/courses");
}
}
const onSubmit = async (values: any) => {
const sections = values?.sections || [];
const deptIds = values?.deptIds || [];
@ -172,6 +189,7 @@ export function CourseFormProvider({
course,
taxonomies,
form,
handleDeleteCourse
}}>
<Form
// requiredMark="optional"

View File

@ -25,8 +25,11 @@ import { CourseSectionEmpty } from "./CourseSectionEmpty";
import { SortableSection } from "./SortableSection";
import { LectureList } from "./LectureList";
import toast from "react-hot-toast";
import { useQueryClient } from "@tanstack/react-query";
import { getQueryKey } from "@trpc/react-query";
const CourseContentForm: React.FC = () => {
const queryClient = useQueryClient();
const { editId } = useCourseEditor();
const sensors = useSensors(
useSensor(PointerSensor),
@ -71,7 +74,11 @@ const CourseContentForm: React.FC = () => {
ids: newItems.map((item) => item.id),
});
};
const softDeletePostDescendant = api.post.softDeletePostDescendant.useMutation({
onSuccess:()=>{
queryClient.invalidateQueries({ queryKey: getQueryKey(api.post) });
}
})
return (
<div className="max-w-4xl mx-auto p-6">
<CourseContentFormHeader />
@ -93,8 +100,8 @@ const CourseContentForm: React.FC = () => {
field={section}
remove={async () => {
if (section?.id) {
await softDeleteByIds.mutateAsync({
ids: [section.id],
await softDeletePostDescendant.mutateAsync({
ancestorId: section.id,
});
}
setItems(sections);

View File

@ -3,6 +3,8 @@ import {
CaretRightOutlined,
SaveOutlined,
CaretDownOutlined,
PaperClipOutlined,
PlaySquareOutlined,
} from "@ant-design/icons";
import { Form, Button, Input, Select, Space } from "antd";
import React, { useState } from "react";
@ -175,11 +177,13 @@ export const SortableLecture: React.FC<SortableLectureProps> = ({
/>
</Form.Item>
</div>
<div className="mt-4 flex flex-1 ">
<div className="mt-4 flex flex-2 flex-row gap-x-5 ">
{lectureType === LectureType.VIDEO ? (
<>
<div className="mb-0 flex-1">
{/* <span className="inline-block w-full h-7 my-1 rounded-lg bg-slate-100 text-center leading-7">添加视频</span> */}
<Form.Item
name={["meta", "videoIds"]}
className="mb-0 flex-1"
rules={[
{
required: true,
@ -189,8 +193,26 @@ export const SortableLecture: React.FC<SortableLectureProps> = ({
<TusUploader
allowTypes={videoMimeTypes}
multiple={false}
style={"h-64"}
icon={<PlaySquareOutlined />}
description="点击或拖拽视频到此区域进行上传"
/>
</Form.Item>
</div>
<div className="mb-0 flex-1">
{/* <span className="inline-block w-full h-7 my-1 rounded-lg bg-slate-100 text-center leading-7">添加附件</span> */}
<Form.Item
name={["meta", "fileIds"]}>
<TusUploader
style={"h-64"}
multiple={true}
icon={<PaperClipOutlined />}
description="点击或拖拽附件到此区域进行上传"
/>
</Form.Item>
</div>
</>
) : (
<div>
<Form.Item
@ -204,7 +226,7 @@ export const SortableLecture: React.FC<SortableLectureProps> = ({
]}>
<QuillEditor
style={{
width:"700px",
width: "700px",
}}
></QuillEditor>
</Form.Item>

View File

@ -1,10 +1,11 @@
import { ArrowLeftOutlined, ClockCircleOutlined } from "@ant-design/icons";
import { Button, Tag, Typography } from "antd";
import { ArrowLeftOutlined, ClockCircleOutlined, ExclamationCircleFilled } from "@ant-design/icons";
import { Button, Modal, Tag, Typography } from "antd";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { CourseStatus, CourseStatusLabel } from "@nice/common";
import { useCourseEditor } from "../context/CourseEditorContext";
import { useAuth } from "@web/src/providers/auth-provider";
import toast from "react-hot-toast";
const { Title } = Typography;
@ -18,8 +19,8 @@ const courseStatusVariant: Record<CourseStatus, string> = {
export default function CourseEditorHeader() {
const navigate = useNavigate();
const { user, hasSomePermissions } = useAuth();
const { onSubmit, course, form } = useCourseEditor();
const { confirm } = Modal;
const { onSubmit, course, form, handleDeleteCourse, editId } = useCourseEditor();
const handleSave = () => {
try {
@ -30,7 +31,26 @@ export default function CourseEditorHeader() {
console.log(err);
}
};
const showDeleteConfirm = () => {
confirm({
title: '确定删除该课程吗',
icon: <ExclamationCircleFilled />,
content: '',
okText: '删除',
okType: 'danger',
cancelText: '取消',
async onOk() {
console.log('OK');
console.log(editId)
await handleDeleteCourse()
toast.success('课程已删除')
navigate("/courses");
},
onCancel() {
console.log('Cancel');
},
});
};
return (
<header className="fixed top-0 left-0 right-0 h-16 bg-white border-b border-gray-200 z-10">
<div className="h-full flex items-center justify-between px-3 md:px-4">
@ -70,7 +90,17 @@ export default function CourseEditorHeader() {
)} */}
</div>
</div>
<div>
{editId &&
<Button
danger
onClick={showDeleteConfirm}
>
</Button>
}
<Button
className="ml-4"
type="primary"
// size="small"
onClick={handleSave}
@ -81,6 +111,7 @@ export default function CourseEditorHeader() {
</Button>
</div>
</div>
</header>
);
}

View File

@ -44,7 +44,8 @@ export default function PostList({
const posts = useMemo(() => {
if (data && !isLoading) {
return data?.items;
console.log(data?.items)
return data?.items.filter(item=>item.deletedAt === null);
}
return [];
}, [data, isLoading]);

View File

@ -13,6 +13,7 @@ export default function PostSelect({
placeholder = "请选择课时",
params = { where: {}, select: {} },
className,
createdById,
}: {
value?: string | string[];
onChange?: (value: string | string[]) => void;
@ -22,6 +23,7 @@ export default function PostSelect({
select?: Prisma.PostSelect<DefaultArgs>;
};
className?: string;
createdById?: string;
}) {
const [searchValue, setSearch] = useState("");
const searchCondition: Prisma.PostWhereInput = useMemo(() => {

BIN
apps/web/web-dist.zip Normal file

Binary file not shown.

View File

@ -15,7 +15,7 @@ done
if [ -f "/usr/share/nginx/html/index.html" ]; then
# Use envsubst to replace environment variable placeholders
echo "Processing /usr/share/nginx/html/index.html"
envsubst < /usr/share/nginx/html/index.temp > /usr/share/nginx/html/index.html.tmp
envsubst < /usr/share/nginx/html/index.template > /usr/share/nginx/html/index.html.tmp
mv /usr/share/nginx/html/index.html.tmp /usr/share/nginx/html/index.html
echo "Processed content:"
cat /usr/share/nginx/html/index.html

View File

@ -100,6 +100,7 @@ export const courseDetailSelect: Prisma.PostSelect = {
// isFeatured: true,
createdAt: true,
updatedAt: true,
deletedAt: true,
// 关联表选择
terms: {
select: {

View File

@ -386,6 +386,9 @@ importers:
react-hot-toast:
specifier: ^2.4.1
version: 2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react-player:
specifier: ^2.16.0
version: 2.16.0(react@18.2.0)
react-resizable:
specifier: ^3.0.5
version: 3.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@ -5603,6 +5606,9 @@ packages:
enquirer:
optional: true
load-script@1.0.0:
resolution: {integrity: sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==}
load-tsconfig@0.2.5:
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -5818,6 +5824,9 @@ packages:
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
engines: {node: '>= 4.0.0'}
memoize-one@5.2.1:
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
meow@8.1.2:
resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==}
engines: {node: '>=10'}
@ -6645,6 +6654,9 @@ packages:
react: '>= 16.3.0'
react-dom: '>= 16.3.0'
react-fast-compare@3.2.2:
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
react-hook-form@7.54.2:
resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==}
engines: {node: '>=18.0.0'}
@ -6664,6 +6676,11 @@ packages:
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
react-player@2.16.0:
resolution: {integrity: sha512-mAIPHfioD7yxO0GNYVFD1303QFtI3lyyQZLY229UEAp/a10cSW+hPcakg0Keq8uWJxT2OiT/4Gt+Lc9bD6bJmQ==}
peerDependencies:
react: '>=16.6.0'
react-refresh@0.14.2:
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
engines: {node: '>=0.10.0'}
@ -13723,6 +13740,8 @@ snapshots:
rfdc: 1.4.1
wrap-ansi: 8.1.0
load-script@1.0.0: {}
load-tsconfig@0.2.5: {}
loader-runner@4.3.0: {}
@ -13899,6 +13918,8 @@ snapshots:
dependencies:
fs-monkey: 1.0.6
memoize-one@5.2.1: {}
meow@8.1.2:
dependencies:
'@types/minimist': 1.2.5
@ -14774,6 +14795,8 @@ snapshots:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-fast-compare@3.2.2: {}
react-hook-form@7.54.2(react@18.2.0):
dependencies:
react: 18.2.0
@ -14789,6 +14812,15 @@ snapshots:
react-is@18.3.1: {}
react-player@2.16.0(react@18.2.0):
dependencies:
deepmerge: 4.3.1
load-script: 1.0.0
memoize-one: 5.2.1
prop-types: 15.8.1
react: 18.2.0
react-fast-compare: 3.2.2
react-refresh@0.14.2: {}
react-resizable@3.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):