From a5c7cc9db36b83b0fd1571f6d8d237b928ac3324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:32:15 +0300 Subject: [PATCH 01/19] pooling fix --- src/pages/Mission.tsx | 49 ++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/pages/Mission.tsx b/src/pages/Mission.tsx index 01814da..aec338d 100644 --- a/src/pages/Mission.tsx +++ b/src/pages/Mission.tsx @@ -27,6 +27,31 @@ const Mission = () => { const pollingRef = useRef(null); const submissions = useAppSelector((state) => state.submin.submitsById[missionIdNumber] || []); + + + + const startPolling = () => { + if (pollingRef.current) + return; + + pollingRef.current = setInterval(async () => { + dispatch(fetchMySubmitsByMission(missionIdNumber)); + + const hasWaiting = submissions.some( + (s: any) => s.solution.status == "Waiting" || s.solution.testerState === "Waiting" + ); + if (!hasWaiting) { + // Всё проверено — стоп + if (pollingRef.current) { + clearInterval(pollingRef.current); + pollingRef.current = null; + } + } + }, 5000); // 10 секунд + }; + + + useEffect(() => { dispatch(fetchMissionById(missionIdNumber)); dispatch(fetchMySubmitsByMission(missionIdNumber)); @@ -59,8 +84,6 @@ const Mission = () => { return
Загрузка...
; } - - interface StatementData { id: number; legend?: string; @@ -116,28 +139,6 @@ const Mission = () => { - const startPolling = () => { - if (pollingRef.current) - return; - - pollingRef.current = setInterval(async () => { - dispatch(fetchMySubmitsByMission(missionIdNumber)); - - const hasWaiting = submissions.some( - (s: any) => s.solution.status == "Waiting" || s.solution.testerState === "Waiting" - ); - if (!hasWaiting) { - // Всё проверено — стоп - if (pollingRef.current) { - clearInterval(pollingRef.current); - pollingRef.current = null; - } - } - }, 5000); // 10 секунд - }; - - - return (
From fbe441c654ae87af1fa50b84c7474882d217b0df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:39:30 +0300 Subject: [PATCH 02/19] pooling content fix --- src/pages/Mission.tsx | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/pages/Mission.tsx b/src/pages/Mission.tsx index aec338d..9b46f48 100644 --- a/src/pages/Mission.tsx +++ b/src/pages/Mission.tsx @@ -26,6 +26,8 @@ const Mission = () => { const pollingRef = useRef(null); const submissions = useAppSelector((state) => state.submin.submitsById[missionIdNumber] || []); + const submissionsRef = useRef(submissions); + @@ -37,7 +39,7 @@ const Mission = () => { pollingRef.current = setInterval(async () => { dispatch(fetchMySubmitsByMission(missionIdNumber)); - const hasWaiting = submissions.some( + const hasWaiting = submissionsRef.current.some( (s: any) => s.solution.status == "Waiting" || s.solution.testerState === "Waiting" ); if (!hasWaiting) { @@ -57,6 +59,9 @@ const Mission = () => { dispatch(fetchMySubmitsByMission(missionIdNumber)); }, [missionIdNumber]); + useEffect(() => { + }, [submissions]); + useEffect(() => { return () => { if (pollingRef.current) { @@ -68,14 +73,16 @@ const Mission = () => { useEffect(() => { - if (submissions.length === 0) return; + submissionsRef.current = submissions; - const hasWaiting = submissions.some( - s => s.solution.status === "Waiting" || s.solution.testerState === "Waiting" - ); + if (submissions.length) { + const hasWaiting = submissions.some( + s => s.solution.status === "Waiting" || s.solution.testerState === "Waiting" + ); - if (hasWaiting) { - startPolling(); + if (hasWaiting) { + startPolling(); + } } }, [submissions]); From db8828e32b12d0ae677ef118d988c3db462865f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:57:24 +0300 Subject: [PATCH 03/19] contests --- src/assets/icons/auth/account.svg | 3 + src/assets/icons/auth/index.ts | 3 +- src/components/button/ReverseButton.tsx | 70 ++++++++ src/pages/Home.tsx | 9 +- src/redux/slices/contests.ts | 189 ++++++++++++++++++++++ src/redux/store.ts | 2 + src/views/home/contests/ContestItem.tsx | 58 +++++-- src/views/home/contests/Contests.tsx | 137 +++++----------- src/views/home/contests/ContestsBlock.tsx | 27 ++-- 9 files changed, 368 insertions(+), 130 deletions(-) create mode 100644 src/assets/icons/auth/account.svg create mode 100644 src/components/button/ReverseButton.tsx create mode 100644 src/redux/slices/contests.ts diff --git a/src/assets/icons/auth/account.svg b/src/assets/icons/auth/account.svg new file mode 100644 index 0000000..b4d5858 --- /dev/null +++ b/src/assets/icons/auth/account.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/auth/index.ts b/src/assets/icons/auth/index.ts index e308253..70db5e3 100644 --- a/src/assets/icons/auth/index.ts +++ b/src/assets/icons/auth/index.ts @@ -1,3 +1,4 @@ import Balloon from "./balloon.svg"; +import Account from "./account.svg" -export {Balloon}; \ No newline at end of file +export {Balloon, Account}; \ No newline at end of file diff --git a/src/components/button/ReverseButton.tsx b/src/components/button/ReverseButton.tsx new file mode 100644 index 0000000..393787a --- /dev/null +++ b/src/components/button/ReverseButton.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import { cn } from "../../lib/cn"; + +interface ButtonProps { + disabled?: boolean; + text?: string; + className?: string; + onClick: () => void; + children?: React.ReactNode; +} + +export const ReverseButton: React.FC = ({ + disabled = false, + text = "", + className, + onClick, + children, +}) => { + return ( + + ); +}; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 4058f4a..f0fbefb 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -36,7 +36,14 @@ const Home = () => { } /> } /> } /> - {name} {dispatch(logout())}}>выйти} /> + +

{jwt}

+ {if (jwt) navigator.clipboard.writeText(jwt);}} text="скопировать токен" className="pt-[20px]"/> +

{name}

+ {dispatch(logout())}}>выйти + + } + />
{ diff --git a/src/redux/slices/contests.ts b/src/redux/slices/contests.ts new file mode 100644 index 0000000..687b228 --- /dev/null +++ b/src/redux/slices/contests.ts @@ -0,0 +1,189 @@ +import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; +import axios from "../../axios"; + +// ===================== +// Типы +// ===================== + +export interface Mission { + missionId: number; + name: string; + sortOrder: number; +} + +export interface Member { + userId: number; + username: string; + role: string; +} + +export interface Contest { + id: number; + name: string; + description: string; + scheduleType: string; + startsAt: string; + endsAt: string; + availableFrom: string | null; + availableUntil: string | null; + attemptDurationMinutes: number | null; + groupId: number | null; + groupName: string | null; + missions: Mission[]; + articles: any[]; + members: Member[]; +} + +interface ContestsResponse { + hasNextPage: boolean; + contests: Contest[]; +} + +export interface CreateContestBody { + name: string; + description: string; + scheduleType: "FixedWindow" | "Flexible"; + startsAt: string; + endsAt: string; + availableFrom: string | null; + availableUntil: string | null; + attemptDurationMinutes: number | null; + groupId: number | null; + missionIds: number[]; + articleIds: number[]; + participantIds: number[]; + organizerIds: number[]; +} + +// ===================== +// Состояние +// ===================== + +interface ContestsState { + contests: Contest[]; + selectedContest: Contest | null; + hasNextPage: boolean; + status: "idle" | "loading" | "successful" | "failed"; + error: string | null; +} + +const initialState: ContestsState = { + contests: [], + selectedContest: null, + hasNextPage: false, + status: "idle", + error: null, +}; + +// ===================== +// Async Thunks +// ===================== + +// Получение списка контестов +export const fetchContests = createAsyncThunk( + "contests/fetchAll", + async ( + params: { page?: number; pageSize?: number; groupId?: number | null } = {}, + { rejectWithValue } + ) => { + try { + const { page = 0, pageSize = 10, groupId } = params; + const response = await axios.get("/contests", { + params: { page, pageSize, groupId }, + }); + return response.data; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Failed to fetch contests"); + } + } +); + +// Получение одного контеста по ID +export const fetchContestById = createAsyncThunk( + "contests/fetchById", + async (id: number, { rejectWithValue }) => { + try { + const response = await axios.get(`/contests/${id}`); + return response.data; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Failed to fetch contest"); + } + } +); + +// Создание нового контеста +export const createContest = createAsyncThunk( + "contests/create", + async (contestData: CreateContestBody, { rejectWithValue }) => { + try { + const response = await axios.post("/contests", contestData); + return response.data; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Failed to create contest"); + } + } +); + +// ===================== +// Slice +// ===================== + +const contestsSlice = createSlice({ + name: "contests", + initialState, + reducers: { + clearSelectedContest: (state) => { + state.selectedContest = null; + }, + }, + extraReducers: (builder) => { + // fetchContests + builder.addCase(fetchContests.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(fetchContests.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + state.contests = action.payload.contests; + state.hasNextPage = action.payload.hasNextPage; + }); + builder.addCase(fetchContests.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + + // fetchContestById + builder.addCase(fetchContestById.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(fetchContestById.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + state.selectedContest = action.payload; + }); + builder.addCase(fetchContestById.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + + // createContest + builder.addCase(createContest.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(createContest.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + state.contests.unshift(action.payload); + }); + builder.addCase(createContest.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + }, +}); + +// ===================== +// Экспорты +// ===================== +export const { clearSelectedContest } = contestsSlice.actions; +export const contestsReducer = contestsSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts index 6ea89a8..872a139 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -3,6 +3,7 @@ import { authReducer } from "./slices/auth"; import { storeReducer } from "./slices/store"; import { missionsReducer } from "./slices/missions"; import { submitReducer } from "./slices/submit"; +import { contestsReducer } from "./slices/contests"; // использование @@ -21,6 +22,7 @@ export const store = configureStore({ store: storeReducer, missions: missionsReducer, submin: submitReducer, + contests: contestsReducer, }, }); diff --git a/src/views/home/contests/ContestItem.tsx b/src/views/home/contests/ContestItem.tsx index 5451ae7..78319cf 100644 --- a/src/views/home/contests/ContestItem.tsx +++ b/src/views/home/contests/ContestItem.tsx @@ -1,11 +1,12 @@ import { cn } from "../../../lib/cn"; +import { Account } from "../../../assets/icons/auth"; +import { registerUser } from "../../../redux/slices/auth"; +import { PrimaryButton } from "../../../components/button/PrimaryButton"; +import { ReverseButton } from "../../../components/button/ReverseButton"; export interface ContestItemProps { - id: number; name: string; - authors: string[]; startAt: string; - registerAt: string; duration: number; members: number; statusRegister: "reg" | "nonreg"; @@ -25,44 +26,69 @@ function formatDate(dateString: string): string { return `${day}/${month}/${year}\n${hours}:${minutes}`; } +function formatWaitTime(ms: number): string { + const minutes = Math.floor(ms / 60000); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + if (days > 0) { + const remainder = days % 10; + let suffix = "дней"; + if (remainder === 1 && days !== 11) suffix = "день"; + else if (remainder >= 2 && remainder <= 4 && (days < 10 || days > 20)) suffix = "дня"; + return `${days} ${suffix}`; + } else if (hours > 0) { + const mins = minutes % 60; + return mins > 0 ? `${hours} ч ${mins} мин` : `${hours} ч`; + } else { + return `${minutes} мин`; + } +} const ContestItem: React.FC = ({ - id, name, authors, startAt, registerAt, duration, members, statusRegister, type + name, startAt, duration, members, statusRegister, type }) => { const now = new Date(); const waitTime = new Date(startAt).getTime() - now.getTime(); return ( -
-
+
{name}
-
- {authors.map((v, i) =>

{v}

)} +
+ {/* {authors.map((v, i) =>

{v}

)} */} + valavshonok
-
+
{formatDate(startAt)}
- {duration} + {formatWaitTime(duration)}
{ waitTime > 0 && -
- {waitTime} +
+ + {"До начала\n" + formatWaitTime(waitTime)}
} -
- {members} +
+
{members}
+
-
- {statusRegister} +
+ { + statusRegister == "reg" ? + <> {}} text="Регистрация"/> + : + <> {}} text="Вы записаны"/> + }
diff --git a/src/views/home/contests/Contests.tsx b/src/views/home/contests/Contests.tsx index 2d68158..d704521 100644 --- a/src/views/home/contests/Contests.tsx +++ b/src/views/home/contests/Contests.tsx @@ -1,128 +1,69 @@ import { useEffect } from "react"; import { SecondaryButton } from "../../../components/button/SecondaryButton"; import { cn } from "../../../lib/cn"; -import { useAppDispatch } from "../../../redux/hooks"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; import ContestsBlock from "./ContestsBlock"; import { setMenuActivePage } from "../../../redux/slices/store"; - - -interface Contest { - id: number; - name: string; - authors: string[]; - startAt: string; - registerAt: string; - duration: number; - members: number; - statusRegister: "reg" | "nonreg"; -} - - +import { fetchContests } from "../../../redux/slices/contests"; const Contests = () => { - const dispatch = useAppDispatch(); const now = new Date(); - const contests: Contest[] = [ - // === Прошедшие контесты === - { - id: 1, - name: "Code Marathon 2025", - authors: ["tourist", "Petr", "Semen", "Rotar"], - startAt: "2025-09-15T10:00:00.000Z", - registerAt: "2025-09-10T10:00:00.000Z", - duration: 180, - members: 4821, - statusRegister: "reg", - }, - { - id: 2, - name: "Autumn Cup 2025", - authors: ["awoo", "Benq"], - startAt: "2025-09-25T17:00:00.000Z", - registerAt: "2025-09-20T17:00:00.000Z", - duration: 150, - members: 3670, - statusRegister: "nonreg", - }, - // === Контесты, которые сейчас идут === - { - id: 3, - name: "Halloween Challenge", - authors: ["Errichto", "Radewoosh"], - startAt: "2025-10-29T10:00:00.000Z", // начался сегодня - registerAt: "2025-10-25T10:00:00.000Z", - duration: 240, - members: 5123, - statusRegister: "reg", - }, - { - id: 4, - name: "October Blitz", - authors: ["neal", "Um_nik"], - startAt: "2025-10-29T12:00:00.000Z", - registerAt: "2025-10-24T12:00:00.000Z", - duration: 300, - members: 2890, - statusRegister: "nonreg", - }, - - // === Контесты, которые еще не начались === - { - id: 5, - name: "Winter Warmup", - authors: ["tourist", "rng_58"], - startAt: "2025-11-05T18:00:00.000Z", - registerAt: "2025-11-01T18:00:00.000Z", - duration: 180, - members: 2100, - statusRegister: "reg", - }, - { - id: 6, - name: "Global Coding Cup", - authors: ["maroonrk", "kostka"], - startAt: "2025-11-12T15:00:00.000Z", - registerAt: "2025-11-08T15:00:00.000Z", - duration: 240, - members: 1520, - statusRegister: "nonreg", - }, - ]; + // Берём данные из Redux + const contests = useAppSelector((state) => state.contests.contests); + const loading = useAppSelector((state) => state.contests.status); + const error = useAppSelector((state) => state.contests.error); + // При загрузке страницы — выставляем активную вкладку и подгружаем контесты useEffect(() => { - dispatch(setMenuActivePage("contests")) + dispatch(setMenuActivePage("contests")); + dispatch(fetchContests({})); }, []); - return ( -
-
+ if (loading == "loading") { + return
Загрузка контестов...
; + } + if (error) { + return
Ошибка: {error}
; + } + + return ( +
+
Контесты
{ }} + onClick={() => {}} text="Создать группу" className="absolute right-0" />
-
+
-
+ { + const endTime = + new Date(contest.endsAt).getTime() + return endTime >= now.getTime(); + })} + /> - - { - const endTime = new Date(contest.startAt).getTime() + contest.duration * 60 * 1000; - return endTime >= now.getTime(); - })} /> - { - const endTime = new Date(contest.startAt).getTime() + contest.duration * 60 * 1000; - return endTime < now.getTime(); - })} /> + { + const endTime = + new Date(contest.endsAt).getTime() + return endTime < now.getTime(); + })} + />
); diff --git a/src/views/home/contests/ContestsBlock.tsx b/src/views/home/contests/ContestsBlock.tsx index ec14e55..ec9642c 100644 --- a/src/views/home/contests/ContestsBlock.tsx +++ b/src/views/home/contests/ContestsBlock.tsx @@ -2,27 +2,19 @@ import { useState, FC } from "react"; import { cn } from "../../../lib/cn"; import { ChevroneDown } from "../../../assets/icons/groups"; import ContestItem from "./ContestItem"; +import { Contest } from "../../../redux/slices/contests"; -interface Contest { - id: number; - name: string; - authors: string[]; - startAt: string; - registerAt: string; - duration: number; - members: number; - statusRegister: "reg" | "nonreg"; -} -interface GroupsBlockProps { + +interface ContestsBlockProps { contests: Contest[]; title: string; className?: string; } -const GroupsBlock: FC = ({ contests, title, className }) => { +const ContestsBlock: FC = ({ contests, title, className }) => { const [active, setActive] = useState(title != "Скрытые"); @@ -50,7 +42,14 @@ const GroupsBlock: FC = ({ contests, title, className }) => {
{ - contests.map((v, i) => ) + contests.map((v, i) => ) }
@@ -60,4 +59,4 @@ const GroupsBlock: FC = ({ contests, title, className }) => { ); }; -export default GroupsBlock; +export default ContestsBlock; From 8429bd40823bb28b8a3037fb7b29f1992775caf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:08:50 +0300 Subject: [PATCH 04/19] copy button --- src/assets/icons/missions/copy-icon.svg | 4 ++ src/assets/icons/missions/index.ts | 4 +- src/views/mission/statement/Statement.tsx | 54 +++++++++++++++++++++-- 3 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 src/assets/icons/missions/copy-icon.svg diff --git a/src/assets/icons/missions/copy-icon.svg b/src/assets/icons/missions/copy-icon.svg new file mode 100644 index 0000000..dd2147a --- /dev/null +++ b/src/assets/icons/missions/copy-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/missions/index.ts b/src/assets/icons/missions/index.ts index 30948a7..62ec89d 100644 --- a/src/assets/icons/missions/index.ts +++ b/src/assets/icons/missions/index.ts @@ -1,4 +1,6 @@ import IconSuccess from "./icon-success.svg" import IconError from "./icon-error.svg" +import CopyIcon from "./copy-icon.svg" -export {IconError, IconSuccess} \ No newline at end of file + +export {IconError, IconSuccess, CopyIcon} \ No newline at end of file diff --git a/src/views/mission/statement/Statement.tsx b/src/views/mission/statement/Statement.tsx index 63449fc..fc2fece 100644 --- a/src/views/mission/statement/Statement.tsx +++ b/src/views/mission/statement/Statement.tsx @@ -1,8 +1,54 @@ -import React from "react"; -// import { cn } from "../../../lib/cn"; +import React, { FC } from "react"; +import { cn } from "../../../lib/cn"; import LaTextContainer from "./LaTextContainer"; +import { CopyIcon } from "../../../assets/icons/missions"; // import FullLatexRenderer from "./FullLatexRenderer"; + + +import { useState } from "react"; + +interface CopyableDivPropd{ + content: string; +} + +const CopyableDiv: FC = ({ content }) => { + const [hovered, setHovered] = useState(false); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(content); + alert("Скопировано!"); + } catch (err) { + console.error("Ошибка копирования:", err); + } + }; + + return ( +
setHovered(true)} + onMouseLeave={() => setHovered(false)} + > + {content} + + + copy + +
+ ); +} + + + + export interface StatementData { id?: number; name?: string; @@ -75,9 +121,9 @@ const Statement: React.FC = ({ {sampleTests.map((v, i) =>
Входные данные
-
{v.input}
+
Выходные данные
-
{v.output}
+
)}
From f2ec4653bb3eaf19609a60e51a6e5fcc71cb8218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Mon, 3 Nov 2025 18:33:20 +0300 Subject: [PATCH 05/19] get groups --- src/pages/Home.tsx | 2 + src/redux/slices/groups.ts | 258 ++++++++++++++++++++++++++ src/redux/store.ts | 2 + src/views/home/groups/Group.tsx | 26 +++ src/views/home/groups/GroupItem.tsx | 9 +- src/views/home/groups/Groups.tsx | 100 +++++----- src/views/home/groups/GroupsBlock.tsx | 11 +- 7 files changed, 355 insertions(+), 53 deletions(-) create mode 100644 src/redux/slices/groups.ts create mode 100644 src/views/home/groups/Group.tsx diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index f0fbefb..99d7b21 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -11,6 +11,7 @@ import Articles from "../views/home/articles/Articles"; import Groups from "../views/home/groups/Groups"; import Contests from "../views/home/contests/Contests"; import { PrimaryButton } from "../components/button/PrimaryButton"; +import Group from "../views/home/groups/Group"; const Home = () => { const name = useAppSelector((state) => state.auth.username); @@ -34,6 +35,7 @@ const Home = () => { } /> } /> } /> + } /> } /> } /> diff --git a/src/redux/slices/groups.ts b/src/redux/slices/groups.ts new file mode 100644 index 0000000..8af10d2 --- /dev/null +++ b/src/redux/slices/groups.ts @@ -0,0 +1,258 @@ +import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; +import axios from "../../axios"; + +// ─── Типы ──────────────────────────────────────────── + +export interface GroupMember { + userId: number; + username: string; + role: string; +} + +export interface Group { + id: number; + name: string; + description: string; + members: GroupMember[]; + contests: any[]; +} + +interface GroupsState { + groups: Group[]; + currentGroup: Group | null; + status: "idle" | "loading" | "successful" | "failed"; + error: string | null; +} + +const initialState: GroupsState = { + groups: [], + currentGroup: null, + status: "idle", + error: null, +}; + +// ─── Async Thunks ───────────────────────────────────── + +// POST /groups +export const createGroup = createAsyncThunk( + "groups/createGroup", + async ( + { name, description }: { name: string; description: string }, + { rejectWithValue } + ) => { + try { + const response = await axios.post("/groups", { name, description }); + return response.data as Group; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Ошибка при создании группы"); + } + } +); + +// PUT /groups/{groupId} +export const updateGroup = createAsyncThunk( + "groups/updateGroup", + async ( + { groupId, name, description }: { groupId: number; name: string; description: string }, + { rejectWithValue } + ) => { + try { + const response = await axios.put(`/groups/${groupId}`, { name, description }); + return response.data as Group; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Ошибка при обновлении группы"); + } + } +); + +// DELETE /groups/{groupId} +export const deleteGroup = createAsyncThunk( + "groups/deleteGroup", + async (groupId: number, { rejectWithValue }) => { + try { + await axios.delete(`/groups/${groupId}`); + return groupId; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Ошибка при удалении группы"); + } + } +); + +// GET /groups/my +export const fetchMyGroups = createAsyncThunk( + "groups/fetchMyGroups", + async (_, { rejectWithValue }) => { + try { + const response = await axios.get("/groups/my"); + return response.data.groups as Group[]; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Ошибка при получении групп"); + } + } +); + +// GET /groups/{groupId} +export const fetchGroupById = createAsyncThunk( + "groups/fetchGroupById", + async (groupId: number, { rejectWithValue }) => { + try { + const response = await axios.get(`/groups/${groupId}`); + return response.data as Group; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Ошибка при получении группы"); + } + } +); + +// POST /groups/members +export const addGroupMember = createAsyncThunk( + "groups/addGroupMember", + async ({ userId, role }: { userId: number; role: string }, { rejectWithValue }) => { + try { + await axios.post("/groups/members", { userId, role }); + return { userId, role }; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Ошибка при добавлении участника"); + } + } +); + +// DELETE /groups/{groupId}/members/{memberId} +export const removeGroupMember = createAsyncThunk( + "groups/removeGroupMember", + async ( + { groupId, memberId }: { groupId: number; memberId: number }, + { rejectWithValue } + ) => { + try { + await axios.delete(`/groups/${groupId}/members/${memberId}`); + return { groupId, memberId }; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Ошибка при удалении участника"); + } + } +); + +// ─── Slice ──────────────────────────────────────────── + +const groupsSlice = createSlice({ + name: "groups", + initialState, + reducers: { + clearCurrentGroup: (state) => { + state.currentGroup = null; + }, + }, + extraReducers: (builder) => { + // ─── CREATE GROUP ─── + builder.addCase(createGroup.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(createGroup.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + state.groups.push(action.payload); + }); + builder.addCase(createGroup.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + + // ─── UPDATE GROUP ─── + builder.addCase(updateGroup.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(updateGroup.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + const index = state.groups.findIndex((g) => g.id === action.payload.id); + if (index !== -1) state.groups[index] = action.payload; + if (state.currentGroup?.id === action.payload.id) + state.currentGroup = action.payload; + }); + builder.addCase(updateGroup.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + + // ─── DELETE GROUP ─── + builder.addCase(deleteGroup.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(deleteGroup.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + state.groups = state.groups.filter((g) => g.id !== action.payload); + if (state.currentGroup?.id === action.payload) state.currentGroup = null; + }); + builder.addCase(deleteGroup.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + + // ─── FETCH MY GROUPS ─── + builder.addCase(fetchMyGroups.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(fetchMyGroups.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + state.groups = action.payload; + }); + builder.addCase(fetchMyGroups.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + + // ─── FETCH GROUP BY ID ─── + builder.addCase(fetchGroupById.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(fetchGroupById.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + state.currentGroup = action.payload; + }); + builder.addCase(fetchGroupById.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + + // ─── ADD MEMBER ─── + builder.addCase(addGroupMember.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(addGroupMember.fulfilled, (state) => { + state.status = "successful"; + }); + builder.addCase(addGroupMember.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + + // ─── REMOVE MEMBER ─── + builder.addCase(removeGroupMember.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase( + removeGroupMember.fulfilled, + (state, action: PayloadAction<{ groupId: number; memberId: number }>) => { + state.status = "successful"; + if (state.currentGroup && state.currentGroup.id === action.payload.groupId) { + state.currentGroup.members = state.currentGroup.members.filter( + (m) => m.userId !== action.payload.memberId + ); + } + } + ); + builder.addCase(removeGroupMember.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + }, +}); + +export const { clearCurrentGroup } = groupsSlice.actions; +export const groupsReducer = groupsSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts index 872a139..3c609e8 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -4,6 +4,7 @@ import { storeReducer } from "./slices/store"; import { missionsReducer } from "./slices/missions"; import { submitReducer } from "./slices/submit"; import { contestsReducer } from "./slices/contests"; +import { groupsReducer } from "./slices/groups"; // использование @@ -23,6 +24,7 @@ export const store = configureStore({ missions: missionsReducer, submin: submitReducer, contests: contestsReducer, + groups: groupsReducer, }, }); diff --git a/src/views/home/groups/Group.tsx b/src/views/home/groups/Group.tsx new file mode 100644 index 0000000..a567e3b --- /dev/null +++ b/src/views/home/groups/Group.tsx @@ -0,0 +1,26 @@ +import { FC } from "react"; +import { cn } from "../../../lib/cn"; +import { useParams, Navigate } from "react-router-dom"; + +interface GroupsBlockProps {} + +const Group: FC = () => { + const { groupId } = useParams<{ groupId: string }>(); + const groupIdNumber = Number(groupId); + + if (!groupId || isNaN(groupIdNumber) || !groupIdNumber) { + return ; + } + + return ( +
+ {groupIdNumber} +
+ ); +}; + +export default Group; diff --git a/src/views/home/groups/GroupItem.tsx b/src/views/home/groups/GroupItem.tsx index d4821d4..1f12097 100644 --- a/src/views/home/groups/GroupItem.tsx +++ b/src/views/home/groups/GroupItem.tsx @@ -1,5 +1,6 @@ import { cn } from "../../../lib/cn"; import { Book, UserAdd, Edit, EyeClosed, EyeOpen } from "../../../assets/icons/groups"; +import { useNavigate } from "react-router-dom"; export interface GroupItemProps { id: number; @@ -26,9 +27,13 @@ const IconComponent: React.FC = ({ const GroupItem: React.FC = ({ id, name, visible, role }) => { + const navigate = useNavigate(); + return ( -
+
navigate(`/group/${id}`)} + >
diff --git a/src/views/home/groups/Groups.tsx b/src/views/home/groups/Groups.tsx index 34559f6..7b7c52e 100644 --- a/src/views/home/groups/Groups.tsx +++ b/src/views/home/groups/Groups.tsx @@ -1,68 +1,84 @@ -import { useEffect } from "react"; +import { useEffect, useMemo } from "react"; import { SecondaryButton } from "../../../components/button/SecondaryButton"; import { cn } from "../../../lib/cn"; -import { useAppDispatch } from "../../../redux/hooks"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; import GroupsBlock from "./GroupsBlock"; import { setMenuActivePage } from "../../../redux/slices/store"; - - -export interface Group { - id: number; - role: "menager" | "member" | "owner" | "viewer"; - visible: boolean; - name: string; -} - +import { fetchMyGroups } from "../../../redux/slices/groups"; const Groups = () => { - const dispatch = useAppDispatch(); - const groups: Group[] = [ - { id: 1, role: "owner", name: "Main Administration", visible: true }, - { id: 2, role: "menager", name: "Project Managers", visible: true }, - { id: 3, role: "member", name: "Developers", visible: true }, - { id: 4, role: "viewer", name: "QA Viewers", visible: true }, - { id: 5, role: "member", name: "Design Team", visible: true }, - { id: 6, role: "owner", name: "Executive Board", visible: true }, - { id: 7, role: "menager", name: "HR Managers", visible: true }, - { id: 8, role: "viewer", name: "Marketing Reviewers", visible: false }, - { id: 9, role: "member", name: "Content Creators", visible: false }, - { id: 10, role: "menager", name: "Support Managers", visible: true }, - { id: 11, role: "viewer", name: "External Auditors", visible: false }, - { id: 12, role: "member", name: "Frontend Developers", visible: true }, - { id: 13, role: "member", name: "Backend Developers", visible: true }, - { id: 14, role: "viewer", name: "Guest Access", visible: false }, - { id: 15, role: "menager", name: "Operations", visible: true }, - ]; + // Берём группы из стора + const groups = useAppSelector((store) => store.groups.groups); + + // Берём текущего пользователя + const currentUserName = useAppSelector((store) => store.auth.username); useEffect(() => { - dispatch(setMenuActivePage("groups")) - }, []); + dispatch(setMenuActivePage("groups")); + dispatch(fetchMyGroups()) + }, [dispatch]); + + // Разделяем группы + const { managedGroups, currentGroups, hiddenGroups } = useMemo(() => { + if (!groups || !currentUserName) { + return { managedGroups: [], currentGroups: [], hiddenGroups: [] }; + } + + const managed: typeof groups = []; + const current: typeof groups = []; + const hidden: typeof groups = []; // пока пустые, без логики + + groups.forEach((group) => { + const me = group.members.find((m) => m.username === currentUserName); + if (!me) return; + + if (me.role === "Administrator") { + managed.push(group); + } else { + current.push(group); + } + }); + + return { managedGroups: managed, currentGroups: current, hiddenGroups: hidden }; + }, [groups, currentUserName]); return ( -
+
-
-
+
Группы
{ }} + onClick={() => {}} text="Создать группу" className="absolute right-0" />
-
+
-
- - - v.visible && (v.role == "owner" || v.role == "menager"))} /> - v.visible && (v.role == "member" || v.role == "viewer"))} /> - v.visible == false)} /> + + +
); diff --git a/src/views/home/groups/GroupsBlock.tsx b/src/views/home/groups/GroupsBlock.tsx index 05e7e76..cb8df35 100644 --- a/src/views/home/groups/GroupsBlock.tsx +++ b/src/views/home/groups/GroupsBlock.tsx @@ -2,14 +2,7 @@ import { useState, FC } from "react"; import GroupItem from "./GroupItem"; import { cn } from "../../../lib/cn"; import { ChevroneDown } from "../../../assets/icons/groups"; - - -export interface Group { - id: number; - role: "menager" | "member" | "owner" | "viewer"; - visible: boolean; - name: string; -} +import { Group } from "../../../redux/slices/groups"; interface GroupsBlockProps { groups: Group[]; @@ -47,7 +40,7 @@ const GroupsBlock: FC = ({ groups, title, className }) => {
{ - groups.map((v, i) => ) + groups.map((v, i) => ) }
From 91aa3e1f80c5a5c997eb94c5ddab491474b321bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Mon, 3 Nov 2025 18:48:27 +0300 Subject: [PATCH 06/19] add model --- src/components/modal/Modal.tsx | 79 ++++++++++++++++++++++++++++++++ src/views/home/groups/Groups.tsx | 11 ++++- 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/components/modal/Modal.tsx diff --git a/src/components/modal/Modal.tsx b/src/components/modal/Modal.tsx new file mode 100644 index 0000000..98e850f --- /dev/null +++ b/src/components/modal/Modal.tsx @@ -0,0 +1,79 @@ +import React from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { cn } from "../../lib/cn"; +import { useClickOutside } from "../../hooks/useClickOutside"; + +type ModalBackdrop = "opaque" | "blur"; + +interface ModalProps { + className?: string; + children?: React.ReactNode; + backdrop?: ModalBackdrop; + open: boolean; + defaultOpen?: boolean; + onOpenChange: (state: boolean) => void; +} + +const modalbgVariants = { + closed: { opacity: 0 }, + open: { opacity: 1 }, +}; + +const modalVariants = { + closed: { opacity: 0, scale: 0.9 }, + open: { opacity: 1, scale: 1 }, +}; + +export const Modal: React.FC = ({ + children, + open, + backdrop, + className, + onOpenChange, +}) => { + const ref = React.useRef(null); + + useClickOutside(ref, () => { + onOpenChange(false); + }); + + return ( +
+ + {open && ( + + )} + +
+ + {open && ( + + {children} + + )} + +
+
+ ); +}; diff --git a/src/views/home/groups/Groups.tsx b/src/views/home/groups/Groups.tsx index 7b7c52e..a4664dc 100644 --- a/src/views/home/groups/Groups.tsx +++ b/src/views/home/groups/Groups.tsx @@ -1,12 +1,14 @@ -import { useEffect, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { SecondaryButton } from "../../../components/button/SecondaryButton"; import { cn } from "../../../lib/cn"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; import GroupsBlock from "./GroupsBlock"; import { setMenuActivePage } from "../../../redux/slices/store"; import { fetchMyGroups } from "../../../redux/slices/groups"; +import { Modal } from "../../../components/modal/Modal"; const Groups = () => { + const [modalActive, setModalActive] = useState(false); const dispatch = useAppDispatch(); // Берём группы из стора @@ -56,7 +58,7 @@ const Groups = () => { Группы
{}} + onClick={() => {setModalActive(true);}} text="Создать группу" className="absolute right-0" /> @@ -80,6 +82,11 @@ const Groups = () => { groups={hiddenGroups} // пока пусто />
+ + + +
modal
+
); }; From 9a2c2a9589d8fcf57df51d7e0d1eafc564374c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Mon, 3 Nov 2025 20:36:29 +0300 Subject: [PATCH 07/19] dont work --- src/components/modal/Modal.tsx | 5 +- src/redux/slices/groups.ts | 91 +++++++++++++-------- src/views/home/groups/GroupItem.tsx | 15 +++- src/views/home/groups/Groups.tsx | 18 +++- src/views/home/groups/ModalCreate.tsx | 43 ++++++++++ src/views/home/groups/ModalUpdate.tsx | 45 ++++++++++ src/views/mission/codeeditor/CodeEditor.tsx | 2 +- 7 files changed, 175 insertions(+), 44 deletions(-) create mode 100644 src/views/home/groups/ModalCreate.tsx create mode 100644 src/views/home/groups/ModalUpdate.tsx diff --git a/src/components/modal/Modal.tsx b/src/components/modal/Modal.tsx index 98e850f..8c25d1e 100644 --- a/src/components/modal/Modal.tsx +++ b/src/components/modal/Modal.tsx @@ -48,9 +48,8 @@ export const Modal: React.FC = ({ transition={{ duration: 0.15 }} className={cn( " fixed top-0 left-0 h-svh w-svw backdrop-filter transition-all z-50", - // " pointer-events-none", - backdrop == "blur" && open ? "backdrop-blur-sm" : "", - backdrop == "opaque" && open ? "bg-[#00000055]" : "" + backdrop == "blur" && open && "backdrop-blur-sm", + backdrop == "opaque" && open && "bg-[#00000055] pointer-events-none", )} > )} diff --git a/src/redux/slices/groups.ts b/src/redux/slices/groups.ts index 8af10d2..c42a9e5 100644 --- a/src/redux/slices/groups.ts +++ b/src/redux/slices/groups.ts @@ -3,6 +3,8 @@ import axios from "../../axios"; // ─── Типы ──────────────────────────────────────────── +type Status = "idle" | "loading" | "successful" | "failed"; + export interface GroupMember { userId: number; username: string; @@ -20,17 +22,34 @@ export interface Group { interface GroupsState { groups: Group[]; currentGroup: Group | null; - status: "idle" | "loading" | "successful" | "failed"; + statuses: { + create: Status; + update: Status; + delete: Status; + fetchMy: Status; + fetchById: Status; + addMember: Status; + removeMember: Status; + }; error: string | null; } const initialState: GroupsState = { groups: [], currentGroup: null, - status: "idle", + statuses: { + create: "idle", + update: "idle", + delete: "idle", + fetchMy: "idle", + fetchById: "idle", + addMember: "idle", + removeMember: "idle", + }, error: null, }; + // ─── Async Thunks ───────────────────────────────────── // POST /groups @@ -146,111 +165,117 @@ const groupsSlice = createSlice({ extraReducers: (builder) => { // ─── CREATE GROUP ─── builder.addCase(createGroup.pending, (state) => { - state.status = "loading"; + state.statuses.create = "loading"; state.error = null; }); builder.addCase(createGroup.fulfilled, (state, action: PayloadAction) => { - state.status = "successful"; + state.statuses.create = "successful"; state.groups.push(action.payload); }); builder.addCase(createGroup.rejected, (state, action: PayloadAction) => { - state.status = "failed"; + state.statuses.create = "failed"; state.error = action.payload; }); + // ─── UPDATE GROUP ─── builder.addCase(updateGroup.pending, (state) => { - state.status = "loading"; + state.statuses.update = "loading"; state.error = null; }); builder.addCase(updateGroup.fulfilled, (state, action: PayloadAction) => { - state.status = "successful"; + state.statuses.update = "successful"; const index = state.groups.findIndex((g) => g.id === action.payload.id); if (index !== -1) state.groups[index] = action.payload; - if (state.currentGroup?.id === action.payload.id) + if (state.currentGroup?.id === action.payload.id) { state.currentGroup = action.payload; + } }); builder.addCase(updateGroup.rejected, (state, action: PayloadAction) => { - state.status = "failed"; + state.statuses.update = "failed"; state.error = action.payload; }); + + // ─── DELETE GROUP ─── builder.addCase(deleteGroup.pending, (state) => { - state.status = "loading"; + state.statuses.delete = "loading"; state.error = null; }); builder.addCase(deleteGroup.fulfilled, (state, action: PayloadAction) => { - state.status = "successful"; + state.statuses.delete = "successful"; state.groups = state.groups.filter((g) => g.id !== action.payload); if (state.currentGroup?.id === action.payload) state.currentGroup = null; }); builder.addCase(deleteGroup.rejected, (state, action: PayloadAction) => { - state.status = "failed"; + state.statuses.delete = "failed"; state.error = action.payload; }); + // ─── FETCH MY GROUPS ─── builder.addCase(fetchMyGroups.pending, (state) => { - state.status = "loading"; + state.statuses.fetchMy = "loading"; state.error = null; }); builder.addCase(fetchMyGroups.fulfilled, (state, action: PayloadAction) => { - state.status = "successful"; + state.statuses.fetchMy = "successful"; state.groups = action.payload; }); builder.addCase(fetchMyGroups.rejected, (state, action: PayloadAction) => { - state.status = "failed"; + state.statuses.fetchMy = "failed"; state.error = action.payload; }); + // ─── FETCH GROUP BY ID ─── builder.addCase(fetchGroupById.pending, (state) => { - state.status = "loading"; + state.statuses.fetchById = "loading"; state.error = null; }); builder.addCase(fetchGroupById.fulfilled, (state, action: PayloadAction) => { - state.status = "successful"; + state.statuses.fetchById = "successful"; state.currentGroup = action.payload; }); builder.addCase(fetchGroupById.rejected, (state, action: PayloadAction) => { - state.status = "failed"; + state.statuses.fetchById = "failed"; state.error = action.payload; }); + // ─── ADD MEMBER ─── builder.addCase(addGroupMember.pending, (state) => { - state.status = "loading"; + state.statuses.addMember = "loading"; state.error = null; }); builder.addCase(addGroupMember.fulfilled, (state) => { - state.status = "successful"; + state.statuses.addMember = "successful"; }); builder.addCase(addGroupMember.rejected, (state, action: PayloadAction) => { - state.status = "failed"; + state.statuses.addMember = "failed"; state.error = action.payload; }); + // ─── REMOVE MEMBER ─── builder.addCase(removeGroupMember.pending, (state) => { - state.status = "loading"; + state.statuses.removeMember = "loading"; state.error = null; }); - builder.addCase( - removeGroupMember.fulfilled, - (state, action: PayloadAction<{ groupId: number; memberId: number }>) => { - state.status = "successful"; - if (state.currentGroup && state.currentGroup.id === action.payload.groupId) { - state.currentGroup.members = state.currentGroup.members.filter( - (m) => m.userId !== action.payload.memberId - ); - } + builder.addCase(removeGroupMember.fulfilled, (state, action: PayloadAction<{ groupId: number; memberId: number }>) => { + state.statuses.removeMember = "successful"; + if (state.currentGroup && state.currentGroup.id === action.payload.groupId) { + state.currentGroup.members = state.currentGroup.members.filter( + (m) => m.userId !== action.payload.memberId + ); } - ); + }); builder.addCase(removeGroupMember.rejected, (state, action: PayloadAction) => { - state.status = "failed"; + state.statuses.removeMember = "failed"; state.error = action.payload; }); + }, }); diff --git a/src/views/home/groups/GroupItem.tsx b/src/views/home/groups/GroupItem.tsx index 1f12097..0bab197 100644 --- a/src/views/home/groups/GroupItem.tsx +++ b/src/views/home/groups/GroupItem.tsx @@ -1,31 +1,37 @@ import { cn } from "../../../lib/cn"; import { Book, UserAdd, Edit, EyeClosed, EyeOpen } from "../../../assets/icons/groups"; import { useNavigate } from "react-router-dom"; +import { GroupUpdate } from "./Groups"; export interface GroupItemProps { id: number; role: "menager" | "member" | "owner" | "viewer"; visible: boolean; name: string; + description: string; + setUpdateActive: (value: any) => void; + setUpdateGroup: (value: GroupUpdate) => void; } interface IconComponentProps { src: string; + onClick?: () => void; } const IconComponent: React.FC = ({ - src + src, onClick = () => void }) => { return onClick()} className="hover:bg-liquid-light rounded-[5px] cursor-pointer transition-all duration-300" /> } const GroupItem: React.FC = ({ - id, name, visible, role + id, name, visible, role, description, setUpdateGroup, setUpdateActive }) => { const navigate = useNavigate(); @@ -45,7 +51,10 @@ const GroupItem: React.FC = ({ (role == "menager" || role == "owner") && } { - (role == "menager" || role == "owner") && + (role == "menager" || role == "owner") && { + setUpdateGroup({id, }); + setUpdateActive(true); + }} /> } { visible == false && diff --git a/src/views/home/groups/Groups.tsx b/src/views/home/groups/Groups.tsx index a4664dc..79f1f09 100644 --- a/src/views/home/groups/Groups.tsx +++ b/src/views/home/groups/Groups.tsx @@ -5,11 +5,22 @@ import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; import GroupsBlock from "./GroupsBlock"; import { setMenuActivePage } from "../../../redux/slices/store"; import { fetchMyGroups } from "../../../redux/slices/groups"; -import { Modal } from "../../../components/modal/Modal"; +import ModalCreate from "./ModalCreate"; +import ModalUpdate from "./ModalUpdate"; + +export interface GroupUpdate { + id: number; + name: string; + description: string; +} const Groups = () => { const [modalActive, setModalActive] = useState(false); + const [modelUpdateActive, setModalUpdateActive] = useState(false); + const [updateGroup, setUpdateGroup] = useState({id: 0, name: "", description: ""}); + const dispatch = useAppDispatch(); + // Берём группы из стора const groups = useAppSelector((store) => store.groups.groups); @@ -84,9 +95,8 @@ const Groups = () => {
- -
modal
-
+ +
); }; diff --git a/src/views/home/groups/ModalCreate.tsx b/src/views/home/groups/ModalCreate.tsx new file mode 100644 index 0000000..c77c82d --- /dev/null +++ b/src/views/home/groups/ModalCreate.tsx @@ -0,0 +1,43 @@ +import { FC, useEffect, useState } from "react"; +import { Modal } from "../../../components/modal/Modal"; +import { PrimaryButton } from "../../../components/button/PrimaryButton"; +import { SecondaryButton } from "../../../components/button/SecondaryButton"; +import { Input } from "../../../components/input/Input"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; +import { createGroup } from "../../../redux/slices/groups"; + +interface ModalCreateProps { + active: boolean; + setActive: (value: boolean) => void; +} + +const ModalCreate: FC = ({ active, setActive }) => { + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const status = useAppSelector((state) => state.groups.statuses.create); + const dispatch = useAppDispatch(); + + useEffect(() => { + if (status == "successful"){ + setActive(false); + } + }, [status]); + + return ( + +
+
Создать группу
+ { setName(v)}} placeholder="login" /> + { setDescription(v)}} placeholder="login" /> + +
+ {dispatch(createGroup({name, description}))}} text="Создать" disabled={status=="loading"}/> + {setActive(false);}} text="Отмена" /> +
+
+
+ ); +}; + +export default ModalCreate; + diff --git a/src/views/home/groups/ModalUpdate.tsx b/src/views/home/groups/ModalUpdate.tsx new file mode 100644 index 0000000..f2f3548 --- /dev/null +++ b/src/views/home/groups/ModalUpdate.tsx @@ -0,0 +1,45 @@ +import { FC, useEffect, useState } from "react"; +import { Modal } from "../../../components/modal/Modal"; +import { PrimaryButton } from "../../../components/button/PrimaryButton"; +import { SecondaryButton } from "../../../components/button/SecondaryButton"; +import { Input } from "../../../components/input/Input"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; +import { createGroup } from "../../../redux/slices/groups"; + +interface ModalUpdateProps { + active: boolean; + setActive: (value: boolean) => void; + groupId: number; + groupName: string; +} + +const ModalUpdate: FC = ({ active, setActive, groupName, groupId }) => { + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const status = useAppSelector((state) => state.groups.statuses.create); + const dispatch = useAppDispatch(); + + useEffect(() => { + if (status == "successful"){ + setActive(false); + } + }, [status]); + + return ( + +
+
Изменить группу {groupName} #{groupId}
+ { setName(v)}} placeholder="login" /> + { setDescription(v)}} placeholder="login" /> + +
+ {dispatch(createGroup({name, description}))}} text="Обновить" disabled={status=="loading"}/> + {setActive(false);}} text="Отмена" /> +
+
+
+ ); +}; + +export default ModalUpdate; + diff --git a/src/views/mission/codeeditor/CodeEditor.tsx b/src/views/mission/codeeditor/CodeEditor.tsx index a9133d9..2e520c6 100644 --- a/src/views/mission/codeeditor/CodeEditor.tsx +++ b/src/views/mission/codeeditor/CodeEditor.tsx @@ -6,7 +6,7 @@ import { DropDownList } from "../../../components/drop-down-list/DropDownList"; const languageMap: Record = { c: "cpp", - cpp: "cpp", + "C++": "cpp", java: "java", python: "python", pascal: "pascal", From 193234b9e5b6b5388e6f0cb261560ae3a0b595b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Mon, 3 Nov 2025 23:24:20 +0300 Subject: [PATCH 08/19] group update --- src/components/button/PrimaryButton.tsx | 22 ++++++++++++++++++++-- src/components/input/Input.tsx | 2 ++ src/views/home/groups/GroupItem.tsx | 12 +++++++++--- src/views/home/groups/Groups.tsx | 21 +++++++++++++++++---- src/views/home/groups/GroupsBlock.tsx | 15 +++++++++++++-- src/views/home/groups/ModalUpdate.tsx | 25 +++++++++++++++++-------- 6 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/components/button/PrimaryButton.tsx b/src/components/button/PrimaryButton.tsx index 02e01c7..5c763c5 100644 --- a/src/components/button/PrimaryButton.tsx +++ b/src/components/button/PrimaryButton.tsx @@ -7,6 +7,23 @@ interface ButtonProps { className?: string; onClick: () => void; children?: React.ReactNode; + color?: "primary" | "secondary" | "error" | "warning" | "success"; +} + +const ColorBgVariants = { + "primary": "bg-liquid-brightmain group-hover:ring-liquid-brightmain", + "secondary": "bg-liquid-darkmain group-hover:ring-liquid-darkmain", + "error": "bg-liquid-red group-hover:ring-liquid-red", + "warning": "bg-liquid-orange group-hover:ring-liquid-orange", + "success": "bg-liquid-green group-hover:ring-liquid-green", +} + +const ColorTextVariants = { + "primary": "group-hover:text-liquid-brightmain ", + "secondary": "group-hover:text-liquid-brightmain ", + "error": "group-hover:text-liquid-red ", + "warning": "group-hover:text-liquid-orange ", + "success": "group-hover:text-liquid-green ", } export const PrimaryButton: React.FC = ({ @@ -15,6 +32,7 @@ export const PrimaryButton: React.FC = ({ className, onClick, children, + color = "secondary", }) => { return (
); }; diff --git a/src/views/home/groups/GroupsBlock.tsx b/src/views/home/groups/GroupsBlock.tsx index cb8df35..cbc90c7 100644 --- a/src/views/home/groups/GroupsBlock.tsx +++ b/src/views/home/groups/GroupsBlock.tsx @@ -3,15 +3,18 @@ import GroupItem from "./GroupItem"; import { cn } from "../../../lib/cn"; import { ChevroneDown } from "../../../assets/icons/groups"; import { Group } from "../../../redux/slices/groups"; +import { GroupUpdate } from "./Groups"; interface GroupsBlockProps { groups: Group[]; title: string; className?: string; + setUpdateActive: (value: any) => void; + setUpdateGroup: (value: GroupUpdate) => void; } -const GroupsBlock: FC = ({ groups, title, className }) => { +const GroupsBlock: FC = ({ groups, title, className, setUpdateActive, setUpdateGroup }) => { const [active, setActive] = useState(title != "Скрытые"); @@ -40,7 +43,15 @@ const GroupsBlock: FC = ({ groups, title, className }) => {
{ - groups.map((v, i) => ) + groups.map((v, i) => ) }
diff --git a/src/views/home/groups/ModalUpdate.tsx b/src/views/home/groups/ModalUpdate.tsx index f2f3548..347e0db 100644 --- a/src/views/home/groups/ModalUpdate.tsx +++ b/src/views/home/groups/ModalUpdate.tsx @@ -4,36 +4,45 @@ import { PrimaryButton } from "../../../components/button/PrimaryButton"; import { SecondaryButton } from "../../../components/button/SecondaryButton"; import { Input } from "../../../components/input/Input"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; -import { createGroup } from "../../../redux/slices/groups"; +import { createGroup, deleteGroup, updateGroup } from "../../../redux/slices/groups"; interface ModalUpdateProps { active: boolean; setActive: (value: boolean) => void; groupId: number; groupName: string; + groupDescription: string; } -const ModalUpdate: FC = ({ active, setActive, groupName, groupId }) => { +const ModalUpdate: FC = ({ active, setActive, groupName, groupId, groupDescription }) => { const [name, setName] = useState(""); const [description, setDescription] = useState(""); - const status = useAppSelector((state) => state.groups.statuses.create); + const statusUpdate = useAppSelector((state) => state.groups.statuses.update); + const statusDelete = useAppSelector((state) => state.groups.statuses.delete); const dispatch = useAppDispatch(); useEffect(() => { - if (status == "successful"){ + if (statusUpdate == "successful"){ setActive(false); } - }, [status]); + }, [statusUpdate]); + + useEffect(() => { + if (statusDelete == "successful"){ + setActive(false); + } + }, [statusDelete]); return (
Изменить группу {groupName} #{groupId}
- { setName(v)}} placeholder="login" /> - { setDescription(v)}} placeholder="login" /> + { setName(v)}} placeholder="login"/> + { setDescription(v)}} placeholder="login" defaultState={groupDescription}/>
- {dispatch(createGroup({name, description}))}} text="Обновить" disabled={status=="loading"}/> + {dispatch(deleteGroup(groupId))}} text="Удалить" disabled={statusDelete=="loading"} color="error"/> + {dispatch(updateGroup({name, description, groupId}))}} text="Обновить" disabled={statusUpdate=="loading"}/> {setActive(false);}} text="Отмена" />
From 1ef655803af5f06f5538da9546bff91d5662ecf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:44:16 +0300 Subject: [PATCH 09/19] article editor --- package-lock.json | 1792 +++++++++++++++++++++++++++- package.json | 7 + src/App.tsx | 3 +- src/pages/ArticleEditor.tsx | 0 src/views/articleeditor/Editor.tsx | 300 +++++ tailwind.config.js | 3 +- 6 files changed, 2100 insertions(+), 5 deletions(-) create mode 100644 src/pages/ArticleEditor.tsx create mode 100644 src/views/articleeditor/Editor.tsx diff --git a/package-lock.json b/package-lock.json index 799d483..ec3a875 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,16 +10,23 @@ "dependencies": { "@monaco-editor/react": "^4.7.0", "@reduxjs/toolkit": "^2.9.2", + "@tailwindcss/typography": "^0.5.19", "@types/react-redux": "^7.1.33", "axios": "^1.12.2", "clsx": "^2.1.1", "framer-motion": "^11.9.0", + "highlight.js": "^11.11.1", "monaco-editor": "^0.54.0", "postcss": "^8.4.47", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-markdown": "^10.1.0", "react-redux": "^9.2.0", "react-router-dom": "^7.9.4", + "rehype-highlight": "^7.0.2", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "remark-gfm": "^4.0.1", "tailwind-cn": "^1.0.2", "tailwind-merge": "^1.14.0", "tailwindcss": "^3.4.12" @@ -1354,6 +1361,31 @@ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1399,13 +1431,39 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", @@ -1416,6 +1474,21 @@ "hoist-non-react-statics": "^3.3.0" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -1463,6 +1536,12 @@ "@babel/runtime": "^7.9.2" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -1711,6 +1790,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, "node_modules/@vitejs/plugin-react": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", @@ -1880,6 +1965,16 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2007,6 +2102,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -2022,6 +2127,46 @@ "node": ">=4" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -2096,6 +2241,16 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2164,7 +2319,6 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2178,6 +2332,19 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2194,6 +2361,28 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2245,6 +2434,18 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2605,6 +2806,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2615,6 +2826,12 @@ "node": ">=0.10.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3034,6 +3251,203 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-sanitize": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz", + "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "unist-util-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -3043,6 +3457,26 @@ "react-is": "^16.7.0" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3090,6 +3524,36 @@ "node": ">=0.8.19" } }, + "node_modules/inline-style-parser": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.6.tgz", + "integrity": "sha512-gtGXVaBdl5mAes3rPcMedEBm12ibjt1kDMFfheul1wUAOVEJW60voNdMVzVkfLN06O7ZaD/rxhfKgtlgtTbMjg==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3117,6 +3581,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3147,6 +3621,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3166,6 +3650,18 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3324,6 +3820,16 @@ "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3336,6 +3842,21 @@ "loose-envify": "cli.js" } }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3346,6 +3867,16 @@ "yallist": "^3.0.2" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/marked": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", @@ -3367,6 +3898,288 @@ "node": ">= 0.4" } }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3376,6 +4189,569 @@ "node": ">= 8" } }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -3446,7 +4822,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -3598,6 +4973,43 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3846,6 +5258,16 @@ "node": ">= 0.8.0" } }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3913,6 +5335,33 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-redux": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", @@ -4026,6 +5475,118 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, + "node_modules/rehype-highlight": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.2.tgz", + "integrity": "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-text": "^4.0.0", + "lowlight": "^3.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-sanitize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz", + "integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-sanitize": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", @@ -4195,6 +5756,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/state-local": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", @@ -4266,6 +5837,20 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4304,6 +5889,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.19.tgz", + "integrity": "sha512-Ev+SgeqiNGT1ufsXyVC5RrJRXdrkRJ1Gol9Qw7Pb72YCKJXrBvP0ckZhBeVSrw2m06DJpei2528uIpjMb4TsoQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.12" + } + }, + "node_modules/style-to-object": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.12.tgz", + "integrity": "sha512-ddJqYnoT4t97QvN2C95bCgt+m7AAgXjVnkk/jxAfmp7EAB8nnqqZYEbMd3em7/vEomDb2LAQKAy1RFfv41mdNw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.6" + } + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -4458,6 +6061,26 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -4534,6 +6157,107 @@ } } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", @@ -4590,6 +6314,48 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", @@ -4650,6 +6416,16 @@ } } }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4833,6 +6609,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 8587ab6..d07fb1b 100644 --- a/package.json +++ b/package.json @@ -12,16 +12,23 @@ "dependencies": { "@monaco-editor/react": "^4.7.0", "@reduxjs/toolkit": "^2.9.2", + "@tailwindcss/typography": "^0.5.19", "@types/react-redux": "^7.1.33", "axios": "^1.12.2", "clsx": "^2.1.1", "framer-motion": "^11.9.0", + "highlight.js": "^11.11.1", "monaco-editor": "^0.54.0", "postcss": "^8.4.47", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-markdown": "^10.1.0", "react-redux": "^9.2.0", "react-router-dom": "^7.9.4", + "rehype-highlight": "^7.0.2", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "remark-gfm": "^4.0.1", "tailwind-cn": "^1.0.2", "tailwind-merge": "^1.14.0", "tailwindcss": "^3.4.12" diff --git a/src/App.tsx b/src/App.tsx index eab18e1..c6135f4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { Route, Routes } from "react-router-dom"; import Home from "./pages/Home"; import Mission from "./pages/Mission"; import UploadMissionForm from "./views/mission/UploadMissionForm"; +import MarkdownEditor from "./views/articleeditor/Editor"; function App() { return ( @@ -16,7 +17,7 @@ function App() { } /> } /> }/> - } /> + {}}/>} />
diff --git a/src/pages/ArticleEditor.tsx b/src/pages/ArticleEditor.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/views/articleeditor/Editor.tsx b/src/views/articleeditor/Editor.tsx new file mode 100644 index 0000000..6a4327b --- /dev/null +++ b/src/views/articleeditor/Editor.tsx @@ -0,0 +1,300 @@ +import { FC, useEffect, useState } from "react"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import rehypeHighlight from "rehype-highlight"; +import rehypeRaw from "rehype-raw"; +import rehypeSanitize from "rehype-sanitize"; +import "highlight.js/styles/github-dark.css"; +import Header from "../mission/statement/Header"; + +interface MarkdownEditorProps { + defaultValue?: string; + onChange: (value: string) => void; +} + + const MarkdownEditor: FC = ({ + defaultValue, + onChange, +}) => { + const [markdown, setMarkdown] = useState(defaultValue ? defaultValue : + `# 🌙 Добро пожаловать в Markdown-редактор + +Добро пожаловать в **Markdown-редактор**! +Здесь ты можешь писать в формате Markdown и видеть результат **в реальном времени** 👇 + +--- + +## 🧱 1. Форматирование текста + +Вот примеры базового форматирования: + +- **Жирный текст** +- *Курсивный текст* +- ***Жирный курсив*** +- ~~Зачёркнутый~~ + +> 💬 _Цитаты_ можно использовать для выделения текста, заметок или описаний. + +--- + +## 🧩 2. Списки + +### 🔹 Маркированный список + +- Один +- Два + - Вложенный уровень + - Ещё глубже +- Три + +### 🔸 Нумерованный список + +1. Первый +2. Второй +3. Третий + 1. Вложенный + 2. Ещё один + +--- + +## ✅ 3. Чеклисты (GFM) + +- [x] Поддержка Markdown +- [x] Подсветка кода +- [x] Таблицы +- [x] Эмодзи 😎 +- [ ] Экспорт в PDF (в будущем) + +--- + +## 💻 4. Код и подсветка + +Пример **TypeScript**: + +\`\`\`tsx +type User = { + name: string; + role: "Разработчик" | "Помощник"; +}; + +function greet(user: User) { + return \`Привет, \${user.name}! 👋 Роль: \${user.role}\`; +} + +console.log(greet({ name: "Ты", role: "Разработчик" })); +\`\`\` + +Пример **JavaScript**: + +\`\`\`js +const sum = (a, b) => a + b; +console.log(sum(2, 3)); // 5 +\`\`\` + +Пример **Python**: + +\`\`\`python +def greet(name): + return f"Привет, {name}! 👋" + +print(greet("Мир")) +\`\`\` + +--- + +## 📊 5. Таблицы (GFM) + +| Имя | Роль | Активен | Эмодзи | +|-------------|----------------|----------|--------| +| ChatGPT | Помощник 🤖 | ✅ | 🤓 | +| Ты | Разработчик 💻 | ✅ | 🚀 | +| TailwindCSS | Стилизация 🎨 | 🟢 | 💅 | + +> Таблицы поддерживают **жирный текст**, _курсив_ и даже \`инлайн-код\` внутри ячеек. + +--- + +## 🔗 6. Ссылки + +- [Документация Markdown](https://www.markdownguide.org/) +- [React Markdown на GitHub](https://github.com/remarkjs/react-markdown) +- Автоматическая ссылка: https://github.com + +--- + +## 🖼️ 7. Изображения + +### Markdown-логотип: + +![Markdown Logo](https://upload.wikimedia.org/wikipedia/commons/4/48/Markdown-mark.svg) + +--- + +## 🧠 8. Цитаты и вложенность + +> 💭 Это обычная цитата. +> +> > А это — **вложенная цитата**. +> > +> > > Можно вкладывать сколько угодно уровней! + +--- + +## ⚙️ 9. Горизонтальные линии + +--- + +*** + +--- + +## 🧮 10. Таблица внутри цитаты + +> Вот таблица прямо внутри блока цитаты: +> +> | Язык | Назначение | +> |-------|-------------| +> | JS | Web-разработка | +> | TS | Строгая типизация | +> | PY | Скрипты и AI | + +--- + +## 🧩 11. Встроенный HTML + +
+ 📂 Раскрывающийся блок + Этот текст виден только после раскрытия! +
    +
  • HTML списки работают
  • +
  • И даже жирный текст
  • +
+
+ +--- +## 🎨 12. Вложенные списки с кодом + +- Этапы: + 1. Создай проект + 2. Добавь зависимости: + \`\`\`bash + npm install react-markdown remark-gfm rehype-highlight highlight.js + \`\`\` + 3. Импортируй стили: + \`\`\`tsx + import "highlight.js/styles/github-dark.css"; + \`\`\` + 4. Готово! + +--- + +## 🚀 13. Финал + +Поздравляю! 🎉 +Ты только что увидел все ключевые возможности **Markdown + GFM** в действии. + +> ✨ Используй этот текст как шаблон для тестирования рендерера. +> 💡 Совет: попробуй поменять тему \`highlight.js\` (например \`monokai.css\` или \`atom-one-dark.css\`). + +--- + +**🖤 Конец демонстрации. Спасибо, что используешь Markdown-редактор!** + +`); + + useEffect(() => { + onChange(markdown); + }, [markdown]) + + return ( +
+
+
+
+ +
+ +
+
+

👀 Предпросмотр

+
+
+ + {markdown} + +
+
+
+
+ + +
+
+

📝 Редактор

+