diff --git a/.eslintrc.json b/.eslintrc.json index 59107204..974b88a7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,7 +14,8 @@ "rules": { "@typescript-eslint/no-non-null-asserted-optional-chain": "off", "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/ban-ts-comment": "off" + "@typescript-eslint/ban-ts-comment": "off", + "react/display-name": "off" } }, { diff --git a/apps/backend/src/api/api.module.ts b/apps/backend/src/api/api.module.ts index 2fa493db..b613fec1 100644 --- a/apps/backend/src/api/api.module.ts +++ b/apps/backend/src/api/api.module.ts @@ -11,15 +11,21 @@ import {PermissionsService} from "@gitroom/backend/services/auth/permissions/per import {IntegrationsController} from "@gitroom/backend/api/routes/integrations.controller"; import {IntegrationManager} from "@gitroom/nestjs-libraries/integrations/integration.manager"; import {SettingsController} from "@gitroom/backend/api/routes/settings.controller"; +import {BullMqModule} from "@gitroom/nestjs-libraries/bull-mq-transport/bull-mq.module"; +import {ioRedis} from "@gitroom/nestjs-libraries/redis/redis.service"; +import {PostsController} from "@gitroom/backend/api/routes/posts.controller"; const authenticatedController = [ UsersController, AnalyticsController, IntegrationsController, - SettingsController + SettingsController, + PostsController ]; @Module({ - imports: [], + imports: [BullMqModule.forRoot({ + connection: ioRedis + })], controllers: [StripeController, AuthController, ...authenticatedController], providers: [ AuthService, @@ -27,7 +33,7 @@ const authenticatedController = [ AuthMiddleware, PoliciesGuard, PermissionsService, - IntegrationManager + IntegrationManager, ], get exports() { return [...this.imports, ...this.providers]; diff --git a/apps/backend/src/api/routes/auth.controller.ts b/apps/backend/src/api/routes/auth.controller.ts index 6ca2e4b2..579e10d7 100644 --- a/apps/backend/src/api/routes/auth.controller.ts +++ b/apps/backend/src/api/routes/auth.controller.ts @@ -41,7 +41,6 @@ export class AuthController { @Res({ passthrough: true }) response: Response ) { try { - console.log('heghefrgefg'); const jwt = await this._authService.routeAuth(body.provider, body); response.cookie('auth', jwt, { domain: '.' + new URL(process.env.FRONTEND_URL!).hostname, diff --git a/apps/backend/src/api/routes/integrations.controller.ts b/apps/backend/src/api/routes/integrations.controller.ts index ca9d9d37..9e645b88 100644 --- a/apps/backend/src/api/routes/integrations.controller.ts +++ b/apps/backend/src/api/routes/integrations.controller.ts @@ -5,6 +5,7 @@ import {IntegrationManager} from "@gitroom/nestjs-libraries/integrations/integra import {IntegrationService} from "@gitroom/nestjs-libraries/database/prisma/integrations/integration.service"; import {GetOrgFromRequest} from "@gitroom/nestjs-libraries/user/org.from.request"; import {Organization} from "@prisma/client"; +import {ApiKeyDto} from "@gitroom/nestjs-libraries/dtos/integrations/api.key.dto"; @Controller('/integrations') export class IntegrationsController { @@ -13,6 +14,18 @@ export class IntegrationsController { private _integrationService: IntegrationService ) { } + @Get('/') + getIntegration() { + return this._integrationManager.getAllIntegrations(); + } + + @Get('/list') + async getIntegrationList( + @GetOrgFromRequest() org: Organization, + ) { + return {integrations: (await this._integrationService.getIntegrationsList(org.id)).map(p => ({name: p.name, id: p.id, picture: p.picture, identifier: p.providerIdentifier, type: p.type}))}; + } + @Get('/social/:integration') async getIntegrationUrl( @Param('integration') integration: string @@ -25,14 +38,14 @@ export class IntegrationsController { const {codeVerifier, state, url} = await integrationProvider.generateAuthUrl(); await ioRedis.set(`login:${state}`, codeVerifier, 'EX', 300); - return url; + return {url}; } @Post('/article/:integration/connect') async connectArticle( @GetOrgFromRequest() org: Organization, @Param('integration') integration: string, - @Body('code') api: string + @Body() api: ApiKeyDto ) { if (!this._integrationManager.getAllowedArticlesIntegrations().includes(integration)) { throw new Error('Integration not allowed'); @@ -43,13 +56,13 @@ export class IntegrationsController { } const integrationProvider = this._integrationManager.getArticlesIntegration(integration); - const {id, name, token} = await integrationProvider.authenticate(api); + const {id, name, token, picture} = await integrationProvider.authenticate(api.api); if (!id) { throw new Error('Invalid api key'); } - return this._integrationService.createIntegration(org.id, name, 'article', String(id), integration, token); + return this._integrationService.createIntegration(org.id, name, picture,'article', String(id), integration, token); } @Post('/social/:integration/connect') @@ -68,7 +81,7 @@ export class IntegrationsController { } const integrationProvider = this._integrationManager.getSocialIntegration(integration); - const {accessToken, expiresIn, refreshToken, id, name} = await integrationProvider.authenticate({ + const {accessToken, expiresIn, refreshToken, id, name, picture} = await integrationProvider.authenticate({ code: body.code, codeVerifier: getCodeVerifier }); @@ -77,6 +90,6 @@ export class IntegrationsController { throw new Error('Invalid api key'); } - return this._integrationService.createIntegration(org.id, name, 'social', String(id), integration, accessToken, refreshToken, expiresIn); + return this._integrationService.createIntegration(org.id, name, picture, 'social', String(id), integration, accessToken, refreshToken, expiresIn); } } diff --git a/apps/backend/src/api/routes/posts.controller.ts b/apps/backend/src/api/routes/posts.controller.ts new file mode 100644 index 00000000..3e6950ec --- /dev/null +++ b/apps/backend/src/api/routes/posts.controller.ts @@ -0,0 +1,21 @@ +import {Body, Controller, Post} from '@nestjs/common'; +import {PostsService} from "@gitroom/nestjs-libraries/database/prisma/posts/posts.service"; +import {GetOrgFromRequest} from "@gitroom/nestjs-libraries/user/org.from.request"; +import {Organization} from "@prisma/client"; +import {CreatePostDto} from "@gitroom/nestjs-libraries/dtos/posts/create.post.dto"; + +@Controller('/posts') +export class PostsController { + constructor( + private _postsService: PostsService + ) { + } + + @Post('/') + createPost( + @GetOrgFromRequest() org: Organization, + @Body() body: CreatePostDto + ) { + return this._postsService.createPost(org.id, body); + } +} diff --git a/apps/backend/src/main.ts b/apps/backend/src/main.ts index e2402569..f39d7596 100644 --- a/apps/backend/src/main.ts +++ b/apps/backend/src/main.ts @@ -1,3 +1,5 @@ +process.env.TZ='UTC'; + import cookieParser from 'cookie-parser'; import {Logger, ValidationPipe} from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; diff --git a/apps/frontend/public/icons/platforms/devto.png b/apps/frontend/public/icons/platforms/devto.png new file mode 100644 index 00000000..37caebcc Binary files /dev/null and b/apps/frontend/public/icons/platforms/devto.png differ diff --git a/apps/frontend/public/icons/platforms/hashnode.png b/apps/frontend/public/icons/platforms/hashnode.png new file mode 100644 index 00000000..7e1679b9 Binary files /dev/null and b/apps/frontend/public/icons/platforms/hashnode.png differ diff --git a/apps/frontend/public/icons/platforms/linkedin.png b/apps/frontend/public/icons/platforms/linkedin.png new file mode 100644 index 00000000..3ce04bcd Binary files /dev/null and b/apps/frontend/public/icons/platforms/linkedin.png differ diff --git a/apps/frontend/public/icons/platforms/medium.png b/apps/frontend/public/icons/platforms/medium.png new file mode 100644 index 00000000..9c00fd76 Binary files /dev/null and b/apps/frontend/public/icons/platforms/medium.png differ diff --git a/apps/frontend/public/icons/platforms/reddit.png b/apps/frontend/public/icons/platforms/reddit.png new file mode 100644 index 00000000..14e73ac7 Binary files /dev/null and b/apps/frontend/public/icons/platforms/reddit.png differ diff --git a/apps/frontend/public/icons/platforms/x.png b/apps/frontend/public/icons/platforms/x.png new file mode 100644 index 00000000..59799be4 Binary files /dev/null and b/apps/frontend/public/icons/platforms/x.png differ diff --git a/apps/frontend/src/app/(site)/launches/page.tsx b/apps/frontend/src/app/(site)/launches/page.tsx index fe088ce5..50acc836 100644 --- a/apps/frontend/src/app/(site)/launches/page.tsx +++ b/apps/frontend/src/app/(site)/launches/page.tsx @@ -1,5 +1,9 @@ +import {LaunchesComponent} from "@gitroom/frontend/components/launches/launches.component"; +import {internalFetch} from "@gitroom/helpers/utils/internal.fetch"; + export default async function Index() { + const {integrations} = await (await internalFetch('/integrations/list')).json(); return ( - <>asd + ); } diff --git a/apps/frontend/src/app/(site)/layout.tsx b/apps/frontend/src/app/(site)/layout.tsx index 350f4d76..12b93072 100644 --- a/apps/frontend/src/app/(site)/layout.tsx +++ b/apps/frontend/src/app/(site)/layout.tsx @@ -1,4 +1,3 @@ -import '../global.css'; import {LayoutSettings} from "@gitroom/frontend/components/layout/layout.settings"; export default async function Layout({ children }: { children: React.ReactNode }) { diff --git a/apps/frontend/src/app/api/hello/route.ts b/apps/frontend/src/app/api/hello/route.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/frontend/src/app/global.css b/apps/frontend/src/app/global.css index 2b0ec37a..3ef18c18 100644 --- a/apps/frontend/src/app/global.css +++ b/apps/frontend/src/app/global.css @@ -2,9 +2,9 @@ @tailwind components; @tailwind utilities; -/*body, html {*/ -/* overflow-x: hidden;*/ -/*}*/ +body, html { + background-color: black; +} .box { position: relative; padding: 8px 24px; @@ -41,7 +41,7 @@ } .table1 thead { - background-color: #111423; + background-color: #0F1524; height: 44px; font-size: 12px; border-bottom: 1px solid #28344F; @@ -55,4 +55,22 @@ padding: 16px 24px; font-family: Inter; font-size: 14px; +} + +.swal2-modal { + background-color: black !important; + border: 2px solid #0B101B; +} + +.swal2-modal * { + color: white !important; +} + +.swal2-icon { + color: white !important; + border-color: white !important; +} + +.swal2-confirm { + background-color: #262373 !important; } \ No newline at end of file diff --git a/apps/frontend/src/app/layout.tsx b/apps/frontend/src/app/layout.tsx index 7668f2c4..7177d176 100644 --- a/apps/frontend/src/app/layout.tsx +++ b/apps/frontend/src/app/layout.tsx @@ -1,3 +1,5 @@ +import './global.css'; + import LayoutContext from "@gitroom/frontend/components/layout/layout.context"; import {ReactNode} from "react"; import {Chakra_Petch} from "next/font/google"; diff --git a/apps/frontend/src/components/launches/add.edit.model.tsx b/apps/frontend/src/components/launches/add.edit.model.tsx new file mode 100644 index 00000000..3b513eef --- /dev/null +++ b/apps/frontend/src/components/launches/add.edit.model.tsx @@ -0,0 +1,349 @@ +'use client'; + +import { FC, useCallback, useEffect, useState } from 'react'; +import dayjs from 'dayjs'; +import { Integrations } from '@gitroom/frontend/components/launches/calendar.context'; +import Image from 'next/image'; +import clsx from 'clsx'; +import MDEditor from '@uiw/react-md-editor'; +import { usePreventWindowUnload } from '@gitroom/react/helpers/use.prevent.window.unload'; +import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; +import { useModals } from '@mantine/modals'; +import { ShowAllProviders } from '@gitroom/frontend/components/launches/providers/show.all.providers'; +import { useHideTopEditor } from '@gitroom/frontend/components/launches/helpers/use.hide.top.editor'; +import { Button } from '@gitroom/react/form/button'; +import { IntegrationContext } from '@gitroom/frontend/components/launches/helpers/use.integration'; +import { + getValues, + resetValues, +} from '@gitroom/frontend/components/launches/helpers/use.values'; +import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; +import { + useMoveToIntegration, + useMoveToIntegrationListener, +} from '@gitroom/frontend/components/launches/helpers/use.move.to.integration'; + +export const PickPlatforms: FC<{ + integrations: Integrations[]; + selectedIntegrations: Integrations[]; + onChange: (integrations: Integrations[]) => void; + singleSelect: boolean; +}> = (props) => { + const { integrations, selectedIntegrations, onChange } = props; + const [selectedAccounts, setSelectedAccounts] = + useState(selectedIntegrations); + + useEffect(() => { + if ( + props.singleSelect && + selectedAccounts.length && + integrations.indexOf(selectedAccounts?.[0]) === -1 + ) { + addPlatform(integrations[0])(); + } + }, [integrations, selectedAccounts]); + + useMoveToIntegrationListener(props.singleSelect, (identifier) => { + const findIntegration = integrations.find( + (p) => p.identifier === identifier + ); + if (findIntegration) { + addPlatform(findIntegration)(); + } + }); + + const addPlatform = useCallback( + (integration: Integrations) => async () => { + if (props.singleSelect) { + onChange([integration]); + setSelectedAccounts([integration]); + return; + } + if (selectedAccounts.includes(integration)) { + const changedIntegrations = selectedAccounts.filter( + ({ id }) => id !== integration.id + ); + + if ( + !props.singleSelect && + !(await deleteDialog( + 'Are you sure you want to remove this platform?' + )) + ) { + return; + } + onChange(changedIntegrations); + setSelectedAccounts(changedIntegrations); + } else { + const changedIntegrations = [...selectedAccounts, integration]; + onChange(changedIntegrations); + setSelectedAccounts(changedIntegrations); + } + }, + [selectedAccounts] + ); + return ( +
+ {integrations.map((integration) => + !props.singleSelect ? ( +
+
p.id === integration.id) === + -1 + ? 'grayscale opacity-65' + : 'grayscale-0' + )} + > + {integration.identifier} + {integration.identifier} +
+
+ ) : ( +
+
p.id === integration.id) === + -1 + ? 'bg-sixth' + : 'bg-forth' + )} + > +
+
+ {integration.identifier} + {integration.identifier} +
+
{integration.name}
+
+
+
+ ) + )} +
+ ); +}; + +export const PreviewComponent: FC<{ + integrations: Integrations[]; + editorValue: string[]; +}> = (props) => { + const { integrations, editorValue } = props; + const [selectedIntegrations, setSelectedIntegrations] = useState([ + integrations[0], + ]); + + useEffect(() => { + if (integrations.indexOf(selectedIntegrations[0]) === -1) { + setSelectedIntegrations([integrations[0]]); + } + }, [integrations, selectedIntegrations]); + return ( +
+ + + + +
+ ); +}; +export const AddEditModal: FC<{ + date: dayjs.Dayjs; + integrations: Integrations[]; +}> = (props) => { + const { date, integrations } = props; + + // selected integrations to allow edit + const [selectedIntegrations, setSelectedIntegrations] = useState< + Integrations[] + >([]); + + // value of each editor + const [value, setValue] = useState(['']); + + const fetch = useFetch(); + + // prevent the window exit by mistake + usePreventWindowUnload(true); + + // hook to move the settings in the right place to fix missing fields + const moveToIntegration = useMoveToIntegration(); + + // hook to test if the top editor should be hidden + const showHide = useHideTopEditor(); + + // hook to open a new modal + const modal = useModals(); + + // if the user exit the popup we reset the global variable with all the values + useEffect(() => { + return () => { + resetValues(); + }; + }, []); + + // Change the value of the global editor + const changeValue = useCallback( + (index: number) => (newValue: string) => { + return setValue((prev) => { + prev[index] = newValue; + return [...prev]; + }); + }, + [value] + ); + + // Add another editor + const addValue = useCallback( + (index: number) => () => { + setValue((prev) => { + prev.splice(index + 1, 0, ''); + return [...prev]; + }); + }, + [value] + ); + + // override the close modal to ask the user if he is sure to close + const askClose = useCallback(async () => { + if ( + await deleteDialog( + 'Are you sure you want to close this modal? (all data will be lost)', + 'Yes, close it!' + ) + ) { + modal.closeAll(); + } + }, []); + + // function to send to the server and save + const schedule = useCallback(async () => { + const values = getValues(); + const allKeys = Object.keys(values).map((v) => ({ + integration: integrations.find((p) => p.id === v), + value: values[v].posts, + valid: values[v].isValid, + settings: values[v].settings(), + })); + + for (const key of allKeys) { + if (!key.valid) { + moveToIntegration(key?.integration?.identifier!); + return; + } + } + + await fetch('/posts', { + method: 'POST', + body: JSON.stringify({ + date: date.utc().format('YYYY-MM-DDTHH:mm:ss'), + posts: allKeys, + }), + }); + }, []); + + return ( + <> + +
+ + {!showHide.hideTopEditor ? ( + <> + {value.map((p, index) => ( + <> + 1 ? 150 : 500} + value={p} + preview="edit" + // @ts-ignore + onChange={changeValue(index)} + /> +
+ +
+ + ))} + + ) : ( +
+ Global Editor Hidden +
+ )} + {!!selectedIntegrations.length && ( + + )} + +
+ + ); +}; diff --git a/apps/frontend/src/components/launches/add.provider.component.tsx b/apps/frontend/src/components/launches/add.provider.component.tsx new file mode 100644 index 00000000..b28edd9f --- /dev/null +++ b/apps/frontend/src/components/launches/add.provider.component.tsx @@ -0,0 +1,109 @@ +"use client"; + +import {useModals} from "@mantine/modals"; +import {FC, useCallback} from "react"; +import {useFetch} from "@gitroom/helpers/utils/custom.fetch"; +import {Input} from "@gitroom/react/form/input"; +import {FieldValues, FormProvider, useForm} from "react-hook-form"; +import {Button} from "@gitroom/react/form/button"; +import { classValidatorResolver } from '@hookform/resolvers/class-validator'; +import {ApiKeyDto} from "@gitroom/nestjs-libraries/dtos/integrations/api.key.dto"; +import {useRouter} from "next/navigation"; + +const resolver = classValidatorResolver(ApiKeyDto); + +export const AddProviderButton = () => { + const modal = useModals(); + const fetch = useFetch(); + const openModal = useCallback(async () => { + const data = await (await fetch('/integrations')).json(); + modal.openModal({ + title: 'Add Channel', + children: + }) + }, []); + return ( + + ); +} + +export const ApiModal: FC<{identifier: string, name: string}> = (props) => { + const fetch = useFetch(); + const router = useRouter(); + const modal = useModals(); + const methods = useForm({ + mode: 'onChange', + resolver + }); + + const submit = useCallback(async (data: FieldValues) => { + const add = await fetch(`/integrations/article/${props.identifier}/connect`, { + method: 'POST', + body: JSON.stringify({api: data.api}) + }); + + if (add.ok) { + modal.closeAll(); + router.refresh(); + return ; + } + + methods.setError('api', { + message: 'Invalid API key' + }); + }, []); + + return ( + +
+
+
+
+
+ ) +} +export const AddProviderComponent: FC<{social: Array<{identifier: string, name: string}>, article: Array<{identifier: string, name: string}>}> = (props) => { + const fetch = useFetch(); + const modal = useModals(); + const {social, article} = props; + const getSocialLink = useCallback((identifier: string) => async () => { + const {url} = await (await fetch('/integrations/social/' + identifier)).json(); + window.location.href = url; + }, []); + + const showApiButton = useCallback((identifier: string, name: string) => async () => { + modal.openModal({ + title: `Add ${name}`, + children: + }) + }, []); + return ( +
+
+

Social

+
+ {social.map((item) => ( +
+ {item.name} +
+ ))} +
+
+
+

Articles

+
+ {article.map((item) => ( +
+ {item.name} +
+ ))} +
+
+
+ ) +} \ No newline at end of file diff --git a/apps/frontend/src/components/launches/calendar.context.tsx b/apps/frontend/src/components/launches/calendar.context.tsx new file mode 100644 index 00000000..545e6826 --- /dev/null +++ b/apps/frontend/src/components/launches/calendar.context.tsx @@ -0,0 +1,40 @@ +'use client'; +import weekOfYear from 'dayjs/plugin/weekOfYear'; +import isoWeek from 'dayjs/plugin/isoWeek'; +import utc from 'dayjs/plugin/utc'; + +import {createContext, FC, ReactNode, useContext, useState} from 'react'; +import dayjs from 'dayjs'; + +dayjs.extend(weekOfYear); +dayjs.extend(isoWeek); +dayjs.extend(utc); + +const CalendarContext = createContext({ + currentWeek: dayjs().week(), + integrations: [] as Integrations[], + setFilters: (filters: { currentWeek: number }) => {}, +}); + +export interface Integrations { + name: string; + id: string; + identifier: string; + type: string; + picture: string; +} +export const CalendarWeekProvider: FC<{ children: ReactNode, integrations: Integrations[] }> = ({ + children, + integrations +}) => { + const [filters, setFilters] = useState({ + currentWeek: dayjs().week(), + }); + return ( + + {children} + + ); +}; + +export const useCalendar = () => useContext(CalendarContext); \ No newline at end of file diff --git a/apps/frontend/src/components/launches/calendar.tsx b/apps/frontend/src/components/launches/calendar.tsx new file mode 100644 index 00000000..35e34cdc --- /dev/null +++ b/apps/frontend/src/components/launches/calendar.tsx @@ -0,0 +1,141 @@ +'use client'; + +import { FC, useCallback, useMemo } from 'react'; +import { + useCalendar, +} from '@gitroom/frontend/components/launches/calendar.context'; +import dayjs from 'dayjs'; +import { useModals } from '@mantine/modals'; +import {AddEditModal} from "@gitroom/frontend/components/launches/add.edit.model"; +import clsx from "clsx"; + +const days = [ + '', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', +]; +const hours = [ + '00:00', + '01:00', + '02:00', + '03:00', + '04:00', + '05:00', + '06:00', + '07:00', + '08:00', + '09:00', + '10:00', + '11:00', + '12:00', + '13:00', + '14:00', + '15:00', + '16:00', + '17:00', + '18:00', + '19:00', + '20:00', + '21:00', + '22:00', + '23:00', +]; + +const CalendarColumn: FC<{ day: number; hour: string }> = (props) => { + const { day, hour } = props; + const week = useCalendar(); + const modal = useModals(); + + const getDate = useMemo(() => { + const date = + dayjs().isoWeek(week.currentWeek).isoWeekday(day).format('YYYY-MM-DD') + + 'T' + + hour + + ':00'; + return dayjs(date); + }, [week.currentWeek]); + + const addModal = useCallback(() => { + modal.openModal({ + closeOnClickOutside: false, + closeOnEscape: false, + withCloseButton: false, + children: ( + + ), + size: '80%', + title: `Adding posts for ${getDate.format('DD/MM/YYYY HH:mm')}`, + }); + }, []); + + const isBeforeNow = useMemo(() => { + return getDate.isBefore(dayjs()); + }, [getDate]); + + return ( +
+
+ {isBeforeNow ? '' : '+ Add'} +
+
+ ); +}; + +export const Calendar = () => { + return ( +
+
+ {days.map((day) => ( +
+ {day} +
+ ))} + {hours.map((hour) => + days.map((day, index) => ( + <> + {index === 0 ? ( +
+ {['00', '10', '20', '30', '40', '50'].map((num) => ( +
+ {hour.split(':')[0] + ':' + num} +
+ ))} +
+ ) : ( +
+ {['00', '10', '20', '30', '40', '50'].map((num) => ( + + ))} +
+ )} + + )) + )} +
+
+ ); +}; diff --git a/apps/frontend/src/components/launches/filters.tsx b/apps/frontend/src/components/launches/filters.tsx new file mode 100644 index 00000000..569de78c --- /dev/null +++ b/apps/frontend/src/components/launches/filters.tsx @@ -0,0 +1,9 @@ +"use client"; +import {useCalendar} from "@gitroom/frontend/components/launches/calendar.context"; +import dayjs from "dayjs"; + +export const Filters = () => { + const week = useCalendar(); + const betweenDates = dayjs().isoWeek(week.currentWeek).startOf('isoWeek').format('DD/MM/YYYY') + ' - ' + dayjs().isoWeek(week.currentWeek).endOf('isoWeek').format('DD/MM/YYYY'); + return
week.setFilters({currentWeek: week.currentWeek + 1})}>Week {week.currentWeek} ({betweenDates})
; +}; diff --git a/apps/frontend/src/components/launches/helpers/use.formatting.ts b/apps/frontend/src/components/launches/helpers/use.formatting.ts new file mode 100644 index 00000000..ad2362d8 --- /dev/null +++ b/apps/frontend/src/components/launches/helpers/use.formatting.ts @@ -0,0 +1,30 @@ +import removeMd from "remove-markdown"; +import {useMemo} from "react"; + +export const useFormatting = (text: string[], params: { + removeMarkdown?: boolean, + saveBreaklines?: boolean, + specialFunc?: (text: string) => string, +}) => { + return useMemo(() => { + return text.map((value) => { + let newText = value; + if (params.saveBreaklines) { + newText = newText.replace('\n', '𝔫𝔢𝔴𝔩𝔦𝔫𝔢'); + } + if (params.removeMarkdown) { + newText = removeMd(value); + } + if (params.saveBreaklines) { + newText = newText.replace('𝔫𝔢𝔴𝔩𝔦𝔫𝔢', '\n'); + } + if (params.specialFunc) { + newText = params.specialFunc(newText); + } + return { + text: newText, + count: params.removeMarkdown && params.saveBreaklines ? newText.replace(/\n/g, ' ').length : newText.length, + } + }); + }, [text]); +} \ No newline at end of file diff --git a/apps/frontend/src/components/launches/helpers/use.hide.top.editor.tsx b/apps/frontend/src/components/launches/helpers/use.hide.top.editor.tsx new file mode 100644 index 00000000..858d4af9 --- /dev/null +++ b/apps/frontend/src/components/launches/helpers/use.hide.top.editor.tsx @@ -0,0 +1,34 @@ +"use client"; + +import EventEmitter from 'events'; +import {useEffect, useState} from "react"; + +const emitter = new EventEmitter(); + +export const useHideTopEditor = () => { + const [hideTopEditor, setHideTopEditor] = useState(false); + useEffect(() => { + const hide = () => { + setHideTopEditor(true); + }; + const show = () => { + setHideTopEditor(false); + }; + emitter.on('hide', hide); + emitter.on('show', show); + return () => { + emitter.off('hide', hide); + emitter.off('show', show); + }; + }, []); + + return { + hideTopEditor, + hide: () => { + emitter.emit('hide'); + }, + show: () => { + emitter.emit('show'); + } + } +} \ No newline at end of file diff --git a/apps/frontend/src/components/launches/helpers/use.integration.ts b/apps/frontend/src/components/launches/helpers/use.integration.ts new file mode 100644 index 00000000..515f146b --- /dev/null +++ b/apps/frontend/src/components/launches/helpers/use.integration.ts @@ -0,0 +1,8 @@ +"use client"; + +import {createContext, useContext} from "react"; +import {Integrations} from "@gitroom/frontend/components/launches/calendar.context"; + +export const IntegrationContext = createContext<{integration: Integrations|undefined, value: string[]}>({integration: undefined, value: []}); + +export const useIntegration = () => useContext(IntegrationContext); \ No newline at end of file diff --git a/apps/frontend/src/components/launches/helpers/use.move.to.integration.tsx b/apps/frontend/src/components/launches/helpers/use.move.to.integration.tsx new file mode 100644 index 00000000..ba4d0f2f --- /dev/null +++ b/apps/frontend/src/components/launches/helpers/use.move.to.integration.tsx @@ -0,0 +1,30 @@ +'use client'; + +import EventEmitter from 'events'; +import {useCallback, useEffect} from 'react'; + +const emitter = new EventEmitter(); +export const useMoveToIntegration = () => { + return useCallback((identifier: string) => { + emitter.emit('moveToIntegration', identifier); + }, []); +}; + +export const useMoveToIntegrationListener = ( + enabled: boolean, + callback: (identifier: string) => void +) => { + useEffect(() => { + if (!enabled) { + return; + } + return load(); + }, []); + + const load = useCallback(() => { + emitter.on('moveToIntegration', callback); + return () => { + emitter.off('moveToIntegration', callback); + }; + }, []); +}; diff --git a/apps/frontend/src/components/launches/helpers/use.values.ts b/apps/frontend/src/components/launches/helpers/use.values.ts new file mode 100644 index 00000000..8732fe5c --- /dev/null +++ b/apps/frontend/src/components/launches/helpers/use.values.ts @@ -0,0 +1,57 @@ +import {useEffect, useMemo} from 'react'; +import { useForm } from 'react-hook-form'; +import { UseFormProps } from 'react-hook-form/dist/types'; +import {allProvidersSettings} from "@gitroom/nestjs-libraries/dtos/posts/providers-settings/all.providers.settings"; +import {classValidatorResolver} from "@hookform/resolvers/class-validator"; + +const finalInformation = {} as { + [key: string]: { posts: string[]; settings: () => object; isValid: boolean }; +}; +export const useValues = (identifier: string, integration: string, value: string[]) => { + const resolver = useMemo(() => { + const findValidator = allProvidersSettings.find((provider) => provider.identifier === identifier)!; + return classValidatorResolver(findValidator?.validator); + }, [integration]); + + const form = useForm({ + resolver + }); + + const getValues = useMemo(() => { + return form.getValues; + }, [form]); + + finalInformation[integration]= finalInformation[integration] || {}; + finalInformation[integration].posts = value; + finalInformation[integration].isValid = form.formState.isValid; + finalInformation[integration].settings = getValues; + + useEffect(() => { + return () => { + delete finalInformation[integration]; + }; + }, []); + + return form; +}; + +export const useSettings = (formProps?: Omit) => { + // const { integration } = useIntegration(); + // const form = useForm({ + // ...formProps, + // mode: 'onChange', + // }); + // + // finalInformation[integration?.identifier!].settings = { + // __type: integration?.identifier!, + // ...form.getValues(), + // }; + // return form; +}; + +export const getValues = () => finalInformation; +export const resetValues = () => { + Object.keys(finalInformation).forEach((key) => { + delete finalInformation[key]; + }); +}; diff --git a/apps/frontend/src/components/launches/launches.component.tsx b/apps/frontend/src/components/launches/launches.component.tsx new file mode 100644 index 00000000..f30e42be --- /dev/null +++ b/apps/frontend/src/components/launches/launches.component.tsx @@ -0,0 +1,61 @@ +import { AddProviderButton } from '@gitroom/frontend/components/launches/add.provider.component'; +import { FC, useMemo } from 'react'; +import Image from 'next/image'; +import { orderBy } from 'lodash'; +import { Calendar } from '@gitroom/frontend/components/launches/calendar'; +import {CalendarWeekProvider, Integrations} from '@gitroom/frontend/components/launches/calendar.context'; +import { Filters } from '@gitroom/frontend/components/launches/filters'; + +export const LaunchesComponent: FC<{ + integrations: Integrations[] +}> = (props) => { + const { integrations } = props; + const sortedIntegrations = useMemo(() => { + return orderBy(integrations, ['type', 'identifier'], ['desc', 'asc']); + }, [integrations]); + return ( + +
+ +
+
+
+

Channels

+
+ {sortedIntegrations.map((integration) => ( +
+
+ {integration.identifier} + {integration.identifier} +
+
{integration.name}
+
3
+
+ ))} +
+ +
+
+ +
+
+
+
+
+ ); +}; diff --git a/apps/frontend/src/components/launches/providers/devto.provider.tsx b/apps/frontend/src/components/launches/providers/devto.provider.tsx new file mode 100644 index 00000000..8c0c338e --- /dev/null +++ b/apps/frontend/src/components/launches/providers/devto.provider.tsx @@ -0,0 +1,12 @@ +import {FC} from "react"; +import {withProvider} from "@gitroom/frontend/components/launches/providers/high.order.provider"; + +const DevtoPreview: FC = () => { + return
asd
+}; + +const DevtoSettings: FC = () => { + return
asdfasd
+}; + +export default withProvider(DevtoSettings, DevtoPreview); \ No newline at end of file diff --git a/apps/frontend/src/components/launches/providers/fonts/x/Chirp-Bold.woff2 b/apps/frontend/src/components/launches/providers/fonts/x/Chirp-Bold.woff2 new file mode 100644 index 00000000..ed3a03d9 Binary files /dev/null and b/apps/frontend/src/components/launches/providers/fonts/x/Chirp-Bold.woff2 differ diff --git a/apps/frontend/src/components/launches/providers/fonts/x/Chirp-Regular.woff2 b/apps/frontend/src/components/launches/providers/fonts/x/Chirp-Regular.woff2 new file mode 100644 index 00000000..53ada57d Binary files /dev/null and b/apps/frontend/src/components/launches/providers/fonts/x/Chirp-Regular.woff2 differ diff --git a/apps/frontend/src/components/launches/providers/high.order.provider.tsx b/apps/frontend/src/components/launches/providers/high.order.provider.tsx new file mode 100644 index 00000000..f823d2e8 --- /dev/null +++ b/apps/frontend/src/components/launches/providers/high.order.provider.tsx @@ -0,0 +1,144 @@ +'use client'; + +import { FC, useCallback, useEffect, useState } from 'react'; +import { Button } from '@gitroom/react/form/button'; +import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; +import MDEditor from '@uiw/react-md-editor'; +import { useHideTopEditor } from '@gitroom/frontend/components/launches/helpers/use.hide.top.editor'; +import { useValues } from '@gitroom/frontend/components/launches/helpers/use.values'; +import { FormProvider } from 'react-hook-form'; +import { useMoveToIntegrationListener } from '@gitroom/frontend/components/launches/helpers/use.move.to.integration'; + +// This is a simple function that if we edit in place, we hide the editor on top +export const EditorWrapper: FC = (props) => { + const showHide = useHideTopEditor(); + useEffect(() => { + showHide.hide(); + return () => { + showHide.show(); + }; + }, []); + + return null; +}; + +export const withProvider = (SettingsComponent: FC, PreviewComponent: FC) => { + return (props: { + identifier: string; + id: string; + value: string[]; + show: boolean; + }) => { + const [editInPlace, setEditInPlace] = useState(false); + const [InPlaceValue, setInPlaceValue] = useState(['']); + const [showTab, setShowTab] = useState(0); + + // in case there is an error on submit, we change to the settings tab for the specific provider + useMoveToIntegrationListener(true, (identifier) => { + if (identifier === props.identifier) { + setShowTab(2); + } + }); + + // this is a smart function, it updates the global value without updating the states (too heavy) and set the settings validation + const form = useValues( + props.identifier, + props.id, + editInPlace ? InPlaceValue : props.value + ); + + // change editor value + const changeValue = useCallback( + (index: number) => (newValue: string) => { + return setInPlaceValue((prev) => { + prev[index] = newValue; + return [...prev]; + }); + }, + [InPlaceValue] + ); + + // add another local editor + const addValue = useCallback( + (index: number) => () => { + setInPlaceValue((prev) => { + prev.splice(index + 1, 0, ''); + return [...prev]; + }); + }, + [InPlaceValue] + ); + + // This is a function if we want to switch from the global editor to edit in place + const changeToEditor = useCallback( + (editor: boolean) => async () => { + if ( + editor && + !editInPlace && + !(await deleteDialog( + 'Are you sure you want to edit in place?', + 'Yes, edit in place!' + )) + ) { + return false; + } + setShowTab(editor ? 1 : 0); + if (editor && !editInPlace) { + setEditInPlace(true); + setInPlaceValue(props.value); + } + }, + [props.value, editInPlace] + ); + + if (!props.show) { + return null; + } + + return ( + +
+ {editInPlace && } +
+
+ +
+
+ +
+
+ +
+
+ {showTab === 1 && ( +
+ {InPlaceValue.map((val, index) => ( + <> + 1 ? 200 : 500} + value={val} + preview="edit" + // @ts-ignore + onChange={changeValue(index)} + /> +
+ +
+ + ))} +
+ )} + {showTab === 2 && } + {showTab === 0 && } +
+
+ ); + }; +}; diff --git a/apps/frontend/src/components/launches/providers/linkedin.provider.tsx b/apps/frontend/src/components/launches/providers/linkedin.provider.tsx new file mode 100644 index 00000000..122a7e2a --- /dev/null +++ b/apps/frontend/src/components/launches/providers/linkedin.provider.tsx @@ -0,0 +1,93 @@ +import { FC } from 'react'; +import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +import localFont from 'next/font/local'; +import clsx from 'clsx'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; +import { useFormatting } from '@gitroom/frontend/components/launches/helpers/use.formatting'; + +const chirp = localFont({ + src: [ + { + path: './fonts/x/Chirp-Regular.woff2', + weight: '400', + style: 'normal', + }, + { + path: './fonts/x/Chirp-Bold.woff2', + weight: '700', + style: 'normal', + }, + ], +}); + +const LinkedinPreview: FC = (props) => { + const { value: topValue, integration } = useIntegration(); + const newValues = useFormatting(topValue, { + removeMarkdown: true, + saveBreaklines: true, + specialFunc: (text: string) => { + return text.slice(0, 280); + } + }); + + return ( +
+
+ {newValues.map((value, index) => ( +
+
+ x + {index !== topValue.length - 1 && ( +
+ )} +
+
+
+
+ {integration?.name} +
+
+ + + + + +
+
+ @username +
+
+
{value.text}
+
+
+ ))} +
+
+ ); +}; + +const LinkedinSettings: FC = () => { + return
asdfasd
; +}; + +export default withProvider(LinkedinSettings, LinkedinPreview); diff --git a/apps/frontend/src/components/launches/providers/reddit.provider.tsx b/apps/frontend/src/components/launches/providers/reddit.provider.tsx new file mode 100644 index 00000000..1093b14d --- /dev/null +++ b/apps/frontend/src/components/launches/providers/reddit.provider.tsx @@ -0,0 +1,93 @@ +import { FC } from 'react'; +import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +import localFont from 'next/font/local'; +import clsx from 'clsx'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; +import { useFormatting } from '@gitroom/frontend/components/launches/helpers/use.formatting'; + +const chirp = localFont({ + src: [ + { + path: './fonts/x/Chirp-Regular.woff2', + weight: '400', + style: 'normal', + }, + { + path: './fonts/x/Chirp-Bold.woff2', + weight: '700', + style: 'normal', + }, + ], +}); + +const RedditPreview: FC = (props) => { + const { value: topValue, integration } = useIntegration(); + const newValues = useFormatting(topValue, { + removeMarkdown: true, + saveBreaklines: true, + specialFunc: (text: string) => { + return text.slice(0, 280); + } + }); + + return ( +
+
+ {newValues.map((value, index) => ( +
+
+ x + {index !== topValue.length - 1 && ( +
+ )} +
+
+
+
+ {integration?.name} +
+
+ + + + + +
+
+ @username +
+
+
{value.text}
+
+
+ ))} +
+
+ ); +}; + +const RedditSettings: FC = () => { + return
asdfasd
; +}; + +export default withProvider(RedditSettings, RedditPreview); diff --git a/apps/frontend/src/components/launches/providers/show.all.providers.tsx b/apps/frontend/src/components/launches/providers/show.all.providers.tsx new file mode 100644 index 00000000..39e72fb8 --- /dev/null +++ b/apps/frontend/src/components/launches/providers/show.all.providers.tsx @@ -0,0 +1,28 @@ +import {FC} from "react"; +import {Integrations} from "@gitroom/frontend/components/launches/calendar.context"; +import DevtoProvider from "@gitroom/frontend/components/launches/providers/devto.provider"; +import XProvider from "@gitroom/frontend/components/launches/providers/x.provider"; +import LinkedinProvider from "@gitroom/frontend/components/launches/providers/linkedin.provider"; +import RedditProvider from "@gitroom/frontend/components/launches/providers/reddit.provider"; + +const Providers = [ + {identifier: 'devto', component: DevtoProvider}, + {identifier: 'x', component: XProvider}, + {identifier: 'linkedin', component: LinkedinProvider}, + {identifier: 'reddit', component: RedditProvider}, +]; + +export const ShowAllProviders: FC<{integrations: Integrations[], value: string[], selectedProvider?: Integrations}> = (props) => { + const {integrations, value, selectedProvider} = props; + return ( + <> + {integrations.map((integration) => { + const {component: ProviderComponent} = Providers.find(provider => provider.identifier === integration.identifier) || {component: null}; + if (!ProviderComponent || integrations.map(p => p.id).indexOf(selectedProvider?.id!) === -1) { + return null; + } + return ; + })} + + ) +} \ No newline at end of file diff --git a/apps/frontend/src/components/launches/providers/x.provider.tsx b/apps/frontend/src/components/launches/providers/x.provider.tsx new file mode 100644 index 00000000..b61a9003 --- /dev/null +++ b/apps/frontend/src/components/launches/providers/x.provider.tsx @@ -0,0 +1,97 @@ +import { FC } from 'react'; +import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +import localFont from 'next/font/local'; +import clsx from 'clsx'; +import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration'; +import { useFormatting } from '@gitroom/frontend/components/launches/helpers/use.formatting'; +import {useSettings} from "@gitroom/frontend/components/launches/helpers/use.values"; + +const chirp = localFont({ + src: [ + { + path: './fonts/x/Chirp-Regular.woff2', + weight: '400', + style: 'normal', + }, + { + path: './fonts/x/Chirp-Bold.woff2', + weight: '700', + style: 'normal', + }, + ], +}); + +const XPreview: FC = (props) => { + const { value: topValue, integration } = useIntegration(); + const newValues = useFormatting(topValue, { + removeMarkdown: true, + saveBreaklines: true, + specialFunc: (text: string) => { + return text.slice(0, 280); + } + }); + + return ( +
+
+ {newValues.map((value, index) => ( +
+
+ x + {index !== topValue.length - 1 && ( +
+ )} +
+
+
+
+ {integration?.name} +
+
+ + + + + +
+
+ @username +
+
+
{value.text}
+
+
+ ))} +
+
+ ); +}; + +const XSettings: FC = () => { + const settings = useSettings({ + + }); + return
asdfasd
; +}; + +export default withProvider(XSettings, XPreview); diff --git a/apps/frontend/src/components/layout/layout.settings.tsx b/apps/frontend/src/components/layout/layout.settings.tsx index 55a6f16c..4fda7403 100644 --- a/apps/frontend/src/components/layout/layout.settings.tsx +++ b/apps/frontend/src/components/layout/layout.settings.tsx @@ -4,30 +4,33 @@ import {headers} from "next/headers"; import {ContextWrapper} from "@gitroom/frontend/components/layout/user.context"; import {NotificationComponent} from "@gitroom/frontend/components/notifications/notification.component"; import {TopMenu} from "@gitroom/frontend/components/layout/top.menu"; +import {MantineWrapper} from "@gitroom/react/helpers/mantine.wrapper"; export const LayoutSettings = ({children}: {children: ReactNode}) => { const user = JSON.parse(headers().get('user')!); return ( -
-
-
- Gitroom + +
+
+
+ Gitroom +
+ +
+ +
- -
- -
-
-
-
- - <div className="flex flex-1 flex-col"> - {children} + <div className="flex-1 flex"> + <div className="flex-1 rounded-3xl px-[23px] py-[17px] flex flex-col"> + <Title /> + <div className="flex flex-1 flex-col"> + {children} + </div> </div> </div> </div> - </div> + </MantineWrapper> </ContextWrapper> ); } \ No newline at end of file diff --git a/apps/frontend/src/components/settings/github.component.tsx b/apps/frontend/src/components/settings/github.component.tsx index 8b74ec28..fabd714a 100644 --- a/apps/frontend/src/components/settings/github.component.tsx +++ b/apps/frontend/src/components/settings/github.component.tsx @@ -3,15 +3,26 @@ import Image from "next/image"; import {Button} from "@gitroom/react/form/button"; import {FC, useCallback, useEffect, useState} from "react"; import {useFetch} from "@gitroom/helpers/utils/custom.fetch"; +import {deleteDialog} from "@gitroom/react/helpers/delete.dialog"; -const ConnectedComponent: FC<{id: string, login: string}> = (props) => { - const {id, login} = props; +const ConnectedComponent: FC<{id: string, login: string, deleteRepository: () => void}> = (props) => { + const {id, login, deleteRepository} = props; + const fetch = useFetch(); + const disconnect = useCallback(async () => { + if (!await deleteDialog('Are you sure you want to disconnect this repository?')) { + return ; + } + deleteRepository(); + await fetch(`/settings/repository/${id}`, { + method: 'DELETE' + }); + }, []); return ( <div className="my-[16px] mt-[16px] h-[90px] bg-sixth border-fifth border rounded-[4px] p-[24px]"> <div className="flex items-center gap-[8px] font-[Inter]"> <div><Image src="/icons/github.svg" alt="GitHub" width={40} height={40}/></div> <div className="flex-1"><strong>Connected:</strong> {login}</div> - <Button>Disconnect</Button> + <Button onClick={disconnect}>Disconnect</Button> </div> </div> ); @@ -93,10 +104,18 @@ export const GithubComponent: FC<{ organizations: Array<{ login: string, id: str if (git.id === g.id) { return {id: g.id, login: name}; } - return g; + return git; }) }); - }, []); + }, [githubState]); + + const deleteConnect = useCallback((g: {id: string, login: string}) => () => { + setGithubState((gitlibs) => { + return gitlibs.filter((git, index) => { + return git.id !== g.id; + }) + }); + }, [githubState]); return ( <> @@ -105,7 +124,7 @@ export const GithubComponent: FC<{ organizations: Array<{ login: string, id: str {!g.login ? ( <ConnectComponent setConnected={setConnected(g)} organizations={organizations} {...g} /> ): ( - <ConnectedComponent {...g} /> + <ConnectedComponent deleteRepository={deleteConnect(g)} {...g} /> )} </> ))} diff --git a/apps/frontend/tailwind.config.js b/apps/frontend/tailwind.config.js index 6d4c00c8..84babd43 100644 --- a/apps/frontend/tailwind.config.js +++ b/apps/frontend/tailwind.config.js @@ -20,12 +20,20 @@ module.exports = { primary: '#000', secondary: '#090B13', third: '#080B13', - forth: '#262373', - fifth: '#172034', + forth: '#612AD5', + fifth: '#28344F', sixth: '#0B101B', gray: '#8C8C8C', + input: '#131B2C', + inputText: '#64748B', + tableBorder: '#1F2941' + }, + gridTemplateColumns: { + '13': 'repeat(13, minmax(0, 1fr));' } }, }, - plugins: [], + plugins: [ + require('tailwind-scrollbar') + ], }; \ No newline at end of file diff --git a/apps/workers/src/app/app.module.ts b/apps/workers/src/app/app.module.ts index f0ba9863..99436788 100644 --- a/apps/workers/src/app/app.module.ts +++ b/apps/workers/src/app/app.module.ts @@ -6,12 +6,13 @@ import {DatabaseModule} from "@gitroom/nestjs-libraries/database/prisma/database import {BullMqModule} from "@gitroom/nestjs-libraries/bull-mq-transport/bull-mq.module"; import {ioRedis} from "@gitroom/nestjs-libraries/redis/redis.service"; import {TrendingService} from "@gitroom/nestjs-libraries/services/trending.service"; +import {PostsController} from "@gitroom/workers/app/posts.controller"; @Module({ imports: [RedisModule, DatabaseModule, BullMqModule.forRoot({ connection: ioRedis })], - controllers: [StarsController], + controllers: [StarsController, PostsController], providers: [TrendingService], }) export class AppModule {} diff --git a/apps/workers/src/app/posts.controller.ts b/apps/workers/src/app/posts.controller.ts new file mode 100644 index 00000000..baaa930d --- /dev/null +++ b/apps/workers/src/app/posts.controller.ts @@ -0,0 +1,15 @@ +import {Controller} from '@nestjs/common'; +import {EventPattern, Transport} from '@nestjs/microservices'; +import {PostsService} from "@gitroom/nestjs-libraries/database/prisma/posts/posts.service"; + +@Controller() +export class PostsController { + constructor( + private _postsService: PostsService + ) { + } + @EventPattern('post', Transport.REDIS) + async checkStars(data: {id: string}) { + return this._postsService.post(data.id); + } +} diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/bull-mq.module.ts b/libraries/nestjs-libraries/src/bull-mq-transport/bull-mq.module.ts index be6f4c10..151bfca2 100644 --- a/libraries/nestjs-libraries/src/bull-mq-transport/bull-mq.module.ts +++ b/libraries/nestjs-libraries/src/bull-mq-transport/bull-mq.module.ts @@ -1,9 +1,7 @@ -import { DynamicModule, Module } from '@nestjs/common'; +import { DynamicModule } from '@nestjs/common'; import { BullMqCoreModule } from './bull-mq-core.module'; import { IBullMqModuleOptionsAsync } from './interfaces/bull-mq-module-options-async.interface'; import { IBullMqModuleOptions } from './interfaces/bull-mq-module-options.interface'; - -@Module({}) export class BullMqModule { static forRoot(options: IBullMqModuleOptions): DynamicModule { return { diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/client/bull-mq.client.ts b/libraries/nestjs-libraries/src/bull-mq-transport/client/bull-mq.client.ts index ee428a42..e488ce49 100644 --- a/libraries/nestjs-libraries/src/bull-mq-transport/client/bull-mq.client.ts +++ b/libraries/nestjs-libraries/src/bull-mq-transport/client/bull-mq.client.ts @@ -74,10 +74,21 @@ export class BullMqClient extends ClientProxy { return () => void 0; } + async delay(pattern: string, jobId: string, delay: number) { + const queue = this.getQueue(pattern); + return queue.getJob(jobId).then((job) => job?.changeDelay(delay)); + } + + async delete(pattern: string, jobId: string) { + const queue = this.getQueue(pattern); + return queue.getJob(jobId).then((job) => job?.remove()); + } + protected async dispatchEvent( packet: ReadPacket<IBullMqEvent<any>>, ): Promise<any> { const queue = this.getQueue(packet.pattern); + console.log(packet); await queue.add(packet.pattern, packet.data, { jobId: packet.data.id ?? v4(), ...packet.data.options, diff --git a/libraries/nestjs-libraries/src/database/prisma/database.module.ts b/libraries/nestjs-libraries/src/database/prisma/database.module.ts index 6c8bacf6..549764f1 100644 --- a/libraries/nestjs-libraries/src/database/prisma/database.module.ts +++ b/libraries/nestjs-libraries/src/database/prisma/database.module.ts @@ -11,6 +11,9 @@ import {SubscriptionRepository} from "@gitroom/nestjs-libraries/database/prisma/ import {NotificationService} from "@gitroom/nestjs-libraries/notifications/notification.service"; import {IntegrationService} from "@gitroom/nestjs-libraries/database/prisma/integrations/integration.service"; import {IntegrationRepository} from "@gitroom/nestjs-libraries/database/prisma/integrations/integration.repository"; +import {PostsService} from "@gitroom/nestjs-libraries/database/prisma/posts/posts.service"; +import {PostsRepository} from "@gitroom/nestjs-libraries/database/prisma/posts/posts.repository"; +import {IntegrationManager} from "@gitroom/nestjs-libraries/integrations/integration.manager"; @Global() @Module({ @@ -29,7 +32,10 @@ import {IntegrationRepository} from "@gitroom/nestjs-libraries/database/prisma/i SubscriptionRepository, NotificationService, IntegrationService, - IntegrationRepository + IntegrationRepository, + PostsService, + PostsRepository, + IntegrationManager ], get exports() { return this.providers; diff --git a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts index 691627b5..50a58631 100644 --- a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts @@ -8,13 +8,14 @@ export class IntegrationRepository { ) { } - createIntegration(org: string, name: string, type: 'article' | 'social' , internalId: string, provider: string, token: string, refreshToken = '', expiresIn = 999999999) { + createIntegration(org: string, name: string, picture: string, type: 'article' | 'social' , internalId: string, provider: string, token: string, refreshToken = '', expiresIn = 999999999) { return this._integration.model.integration.create({ data: { type: type as any, name, providerIdentifier: provider, token, + picture, refreshToken, ...expiresIn ? {tokenExpiration: new Date(Date.now() + expiresIn * 1000)} :{}, internalId, @@ -22,4 +23,12 @@ export class IntegrationRepository { } }) } + + getIntegrationsList(org: string) { + return this._integration.model.integration.findMany({ + where: { + organizationId: org + } + }); + } } \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts index 140a10ec..692fdfa4 100644 --- a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts @@ -7,7 +7,11 @@ export class IntegrationService { private _integrationRepository: IntegrationRepository, ) { } - createIntegration(org: string, name: string, type: 'article' | 'social' , internalId: string, provider: string, token: string, refreshToken = '', expiresIn?: number) { - return this._integrationRepository.createIntegration(org, name, type, internalId, provider, token, refreshToken, expiresIn); + createIntegration(org: string, name: string, picture: string, type: 'article' | 'social' , internalId: string, provider: string, token: string, refreshToken = '', expiresIn?: number) { + return this._integrationRepository.createIntegration(org, name, picture, type, internalId, provider, token, refreshToken, expiresIn); + } + + getIntegrationsList(org: string) { + return this._integrationRepository.getIntegrationsList(org); } } diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts new file mode 100644 index 00000000..d12e152b --- /dev/null +++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts @@ -0,0 +1,71 @@ +import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service'; +import { Injectable } from '@nestjs/common'; +import { Post as PostBody } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto'; +import dayjs from 'dayjs'; +import { Integration, Post } from '@prisma/client'; + +@Injectable() +export class PostsRepository { + constructor(private _post: PrismaRepository<'post'>) {} + + getPost(id: string, includeIntegration = false) { + return this._post.model.post.findUnique({ + where: { + id, + }, + include: { + ...(includeIntegration ? { integration: true } : {}), + childrenPost: true, + }, + }); + } + + updatePost(id: string, postId: string, releaseURL: string) { + return this._post.model.post.update({ + where: { + id, + }, + data: { + state: 'PUBLISHED', + releaseURL, + releaseId: postId, + }, + }); + } + + async createPost(orgId: string, date: string, body: PostBody) { + const posts: Post[] = []; + for (const value of body.value) { + posts.push( + await this._post.model.post.create({ + data: { + publishDate: dayjs(date).toDate(), + integration: { + connect: { + id: body.integration.id, + organizationId: orgId, + }, + }, + ...(posts.length + ? { + parentPost: { + connect: { + id: posts[posts.length - 1]?.id, + }, + }, + } + : {}), + content: value, + organization: { + connect: { + id: orgId, + }, + }, + }, + }) + ); + } + + return posts; + } +} diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts new file mode 100644 index 00000000..094f9aba --- /dev/null +++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts @@ -0,0 +1,92 @@ +import { Injectable } from '@nestjs/common'; +import { PostsRepository } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.repository'; +import { CreatePostDto } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto'; +import { BullMqClient } from '@gitroom/nestjs-libraries/bull-mq-transport/client/bull-mq.client'; +import dayjs from 'dayjs'; +import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager'; +import { Integration, Post } from '@prisma/client'; + +type PostWithConditionals = Post & { + integration?: Integration; + childrenPost: Post[]; +}; + +@Injectable() +export class PostsService { + constructor( + private _postRepository: PostsRepository, + private _workerServiceProducer: BullMqClient, + private _integrationManager: IntegrationManager + ) {} + + async getPostsRecursively( + id: string, + includeIntegration = false + ): Promise<PostWithConditionals[]> { + const post = await this._postRepository.getPost(id, includeIntegration); + return [ + post!, + ...(post?.childrenPost?.length + ? await this.getPostsRecursively(post.childrenPost[0].id) + : []), + ]; + } + + async post(id: string) { + const [firstPost, ...morePosts] = await this.getPostsRecursively(id, true); + if (!firstPost) { + return; + } + + if (firstPost.integration?.type === 'article') { + return this.postArticle(firstPost.integration!, [firstPost, ...morePosts]); + } + + return this.postSocial(firstPost.integration!, [firstPost, ...morePosts]); + } + + private async postSocial(integration: Integration, posts: Post[]) { + const getIntegration = this._integrationManager.getSocialIntegration(integration.providerIdentifier); + if (!getIntegration) { + return; + } + + const publishedPosts = await getIntegration.post(integration.internalId, integration.token, posts.map(p => ({ + id: p.id, + message: p.content, + settings: JSON.parse(p.settings || '{}'), + }))); + + for (const post of publishedPosts) { + await this._postRepository.updatePost(post.id, post.postId, post.releaseURL); + } + } + + private async postArticle(integration: Integration, posts: Post[]) { + const getIntegration = this._integrationManager.getArticlesIntegration(integration.providerIdentifier); + if (!getIntegration) { + return; + } + const {postId, releaseURL} = await getIntegration.post(integration.token, posts.map(p => p.content).join('\n\n'), JSON.parse(posts[0].settings || '{}')); + await this._postRepository.updatePost(posts[0].id, postId, releaseURL); + } + + async createPost(orgId: string, body: CreatePostDto) { + for (const post of body.posts) { + const posts = await this._postRepository.createPost( + orgId, + body.date, + post + ); + this._workerServiceProducer.emit('post', { + id: posts[0].id, + options: { + delay: 0 // dayjs(posts[0].publishDate).diff(dayjs(), 'millisecond'), + }, + payload: { + id: posts[0].id, + }, + }); + } + } +} diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index 69396ff9..49b38347 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -128,6 +128,7 @@ model Integration { organizationId String name String organization Organization @relation(fields: [organizationId], references: [id]) + picture String? providerIdentifier String type String token String @@ -183,21 +184,19 @@ model Slots { model Post { id String @id @default(cuid()) state State @default(QUEUE) - queueId String? publishDate DateTime organizationId String - IntegrationId String + integrationId String + content String organization Organization @relation(fields: [organizationId], references: [id]) - Integration Integration @relation(fields: [IntegrationId], references: [id]) + integration Integration @relation(fields: [integrationId], references: [id]) title String? description String? - canonicalUrl String? - canonicalPostId String? parentPostId String? + releaseId String? releaseURL String? - canonicalPost Post? @relation("canonicalPostId", fields: [canonicalPostId], references: [id]) + settings String? parentPost Post? @relation("parentPostId", fields: [parentPostId], references: [id]) - canonicalChildren Post[] @relation("canonicalPostId") childrenPost Post[] @relation("parentPostId") tags PostTag[] media PostMedia[] @@ -207,7 +206,8 @@ model Post { enum State { QUEUE - SENT + PUBLISHED + ERROR DRAFT } diff --git a/libraries/nestjs-libraries/src/database/prisma/stars/stars.service.ts b/libraries/nestjs-libraries/src/database/prisma/stars/stars.service.ts index 404c7deb..d19aa0d3 100644 --- a/libraries/nestjs-libraries/src/database/prisma/stars/stars.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/stars/stars.service.ts @@ -4,7 +4,7 @@ import {chunk, groupBy} from "lodash"; import dayjs from "dayjs"; import {NotificationService} from "@gitroom/nestjs-libraries/notifications/notification.service"; import {StarsListDto} from "@gitroom/nestjs-libraries/dtos/analytics/stars.list.dto"; -import * as console from "console"; +import {BullMqClient} from "@gitroom/nestjs-libraries/bull-mq-transport/client/bull-mq.client"; enum Inform { Removed, New, @@ -14,7 +14,8 @@ enum Inform { export class StarsService { constructor( private _starsRepository: StarsRepository, - private _notificationsService: NotificationService + private _notificationsService: NotificationService, + private _workerServiceProducer: BullMqClient ){} getGitHubRepositoriesByOrgId(org: string) { @@ -49,7 +50,6 @@ export class StarsService { } async syncProcess(login: string, page = 1) { - console.log('processing', login, page); const starsRequest = await fetch(`https://api.github.com/repos/${login}/stargazers?page=${page}&per_page=100`, { headers: { Accept: 'application/vnd.github.v3.star+json', @@ -221,6 +221,7 @@ export class StarsService { } async updateGitHubLogin(orgId: string, id: string, login: string) { + this._workerServiceProducer.emit('sync_all_stars', {payload: {login}}).subscribe(); return this._starsRepository.updateGitHubLogin(orgId, id, login); } diff --git a/libraries/nestjs-libraries/src/dtos/integrations/api.key.dto.ts b/libraries/nestjs-libraries/src/dtos/integrations/api.key.dto.ts new file mode 100644 index 00000000..005d0dd6 --- /dev/null +++ b/libraries/nestjs-libraries/src/dtos/integrations/api.key.dto.ts @@ -0,0 +1,9 @@ +import {IsString, MinLength} from "class-validator"; + +export class ApiKeyDto { + @IsString() + @MinLength(4, { + message: 'Must be at least 4 characters' + }) + api: string; +} \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts new file mode 100644 index 00000000..b47b48c0 --- /dev/null +++ b/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts @@ -0,0 +1,47 @@ +import {ArrayMinSize, IsArray, IsDateString, IsDefined, IsString, ValidateNested} from "class-validator"; +import {Type} from "class-transformer"; +import {DevToSettingsDto} from "@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.settings.dto"; + +export class EmptySettings {} +export class Integration { + @IsDefined() + @IsString() + id: string +} + +export class Post { + @IsDefined() + @Type(() => Integration) + @ValidateNested() + integration: Integration; + + @IsDefined() + @ArrayMinSize(1) + @IsArray() + @IsString({ each: true }) + value: string[]; + + @Type(() => EmptySettings, { + keepDiscriminatorProperty: true, + discriminator: { + property: '__type', + subTypes: [ + { value: DevToSettingsDto, name: 'devto' }, + ], + }, + }) + settings: DevToSettingsDto +} + +export class CreatePostDto { + @IsDefined() + @IsDateString() + date: string; + + @IsDefined() + @Type(() => Post) + @IsArray() + @ValidateNested({each: true}) + @ArrayMinSize(1) + posts: Post[] +} diff --git a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/all.providers.settings.ts b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/all.providers.settings.ts new file mode 100644 index 00000000..d1e91366 --- /dev/null +++ b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/all.providers.settings.ts @@ -0,0 +1,7 @@ +import {DevToSettingsDto} from "@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.settings.dto"; +export const allProvidersSettings = [{ + identifier: 'devto', + validator: DevToSettingsDto +}]; + +export type AllProvidersSettings = DevToSettingsDto; \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/dev.to.settings.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/dev.to.settings.dto.ts new file mode 100644 index 00000000..433a24d5 --- /dev/null +++ b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/dev.to.settings.dto.ts @@ -0,0 +1,24 @@ +import {IsArray, IsDefined, IsOptional, IsString} from "class-validator"; + +export class DevToSettingsDto { + @IsString() + @IsDefined() + title: string; + + @IsString() + @IsOptional() + main_image?: number; + + @IsString() + canonical: string; + + @IsString({ + each: true + }) + @IsArray() + tags: string[]; + + @IsString() + @IsOptional() + organization?: string; +} \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/integrations/article/article.integrations.interface.ts b/libraries/nestjs-libraries/src/integrations/article/article.integrations.interface.ts index 652522f9..d63d509e 100644 --- a/libraries/nestjs-libraries/src/integrations/article/article.integrations.interface.ts +++ b/libraries/nestjs-libraries/src/integrations/article/article.integrations.interface.ts @@ -1,6 +1,6 @@ export interface ArticleIntegrationsInterface { - authenticate(token: string): Promise<{id: string, name: string, token: string}>; - publishPost(token: string, content: string): Promise<string>; + authenticate(token: string): Promise<{id: string, name: string, token: string, picture: string}>; + post(token: string, content: string, settings: object): Promise<{postId: string, releaseURL: string}>; } export interface ArticleProvider extends ArticleIntegrationsInterface { diff --git a/libraries/nestjs-libraries/src/integrations/article/dev.to.provider.ts b/libraries/nestjs-libraries/src/integrations/article/dev.to.provider.ts index 76ac339e..f13ff12e 100644 --- a/libraries/nestjs-libraries/src/integrations/article/dev.to.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/article/dev.to.provider.ts @@ -3,8 +3,8 @@ import {ArticleProvider} from "@gitroom/nestjs-libraries/integrations/article/ar export class DevToProvider implements ArticleProvider { identifier = 'devto'; name = 'Dev.to'; - async authenticate(token: string): Promise<{ id: string; name: string; token: string; }> { - const {name, id} = await (await fetch('https://dev.to/api/users/me', { + async authenticate(token: string) { + const {name, id, profile_image} = await (await fetch('https://dev.to/api/users/me', { headers: { 'api-key': token } @@ -13,11 +13,15 @@ export class DevToProvider implements ArticleProvider { return { id, name, - token + token, + picture: profile_image } } - async publishPost(token: string, content: string): Promise<string> { - return ''; + async post(token: string, content: string, settings: object) { + return { + postId: '123', + releaseURL: 'https://dev.to' + } } } \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/integrations/article/hashnode.provider.ts b/libraries/nestjs-libraries/src/integrations/article/hashnode.provider.ts index c0b96347..73bc245e 100644 --- a/libraries/nestjs-libraries/src/integrations/article/hashnode.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/article/hashnode.provider.ts @@ -3,9 +3,9 @@ import {ArticleIntegrationsInterface, ArticleProvider} from "@gitroom/nestjs-lib export class HashnodeProvider implements ArticleProvider { identifier = 'hashnode'; name = 'Hashnode'; - async authenticate(token: string): Promise<{ id: string; name: string; token: string; }> { + async authenticate(token: string) { try { - const {data: {me: {name, id}}} = await (await fetch('https://gql.hashnode.com', { + const {data: {me: {name, id, profilePicture}}} = await (await fetch('https://gql.hashnode.com', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -16,7 +16,8 @@ export class HashnodeProvider implements ArticleProvider { query { me { name, - id + id, + profilePicture } } ` @@ -24,19 +25,23 @@ export class HashnodeProvider implements ArticleProvider { })).json(); return { - id, name, token + id, name, token, picture: profilePicture } } catch (err) { return { id: '', name: '', - token: '' + token: '', + picture: '' } } } - async publishPost(token: string, content: string): Promise<string> { - return ''; + async post(token: string, content: string, settings: object) { + return { + postId: '123', + releaseURL: 'https://dev.to' + } } } \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/integrations/article/medium.provider.ts b/libraries/nestjs-libraries/src/integrations/article/medium.provider.ts index 005534ee..a28d1de0 100644 --- a/libraries/nestjs-libraries/src/integrations/article/medium.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/article/medium.provider.ts @@ -4,8 +4,8 @@ export class MediumProvider implements ArticleProvider { identifier = 'medium'; name = 'Medium'; - async authenticate(token: string): Promise<{ id: string; name: string; token: string; }> { - const {data: {name, id}} = await (await fetch('https://api.medium.com/v1/me', { + async authenticate(token: string) { + const {data: {name, id, imageUrl}} = await (await fetch('https://api.medium.com/v1/me', { headers: { Authorization: `Bearer ${token}` } @@ -14,11 +14,15 @@ export class MediumProvider implements ArticleProvider { return { id, name, - token + token, + picture: imageUrl } } - async publishPost(token: string, content: string): Promise<string> { - return ''; + async post(token: string, content: string, settings: object) { + return { + postId: '123', + releaseURL: 'https://dev.to' + } } } \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/integrations/integration.manager.ts b/libraries/nestjs-libraries/src/integrations/integration.manager.ts index d19a33a1..17adf2ee 100644 --- a/libraries/nestjs-libraries/src/integrations/integration.manager.ts +++ b/libraries/nestjs-libraries/src/integrations/integration.manager.ts @@ -22,6 +22,12 @@ const articleIntegrationList = [ @Injectable() export class IntegrationManager { + getAllIntegrations() { + return { + social: socialIntegrationList.map(p => ({name: p.name, identifier: p.identifier})), + article: articleIntegrationList.map(p => ({name: p.name, identifier: p.identifier})), + }; + } getAllowedSocialsIntegrations() { return socialIntegrationList.map(p => p.identifier); } diff --git a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts index 7c85c686..b745221f 100644 --- a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts @@ -1,82 +1,202 @@ -import {AuthTokenDetails, PostDetails, PostResponse, SocialProvider} from "@gitroom/nestjs-libraries/integrations/social/social.integrations.interface"; -import {makeId} from "@gitroom/nestjs-libraries/services/make.is"; +import { + AuthTokenDetails, + PostDetails, + PostResponse, + SocialProvider, +} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface'; +import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; export class LinkedinProvider implements SocialProvider { - identifier = 'linkedin'; - name = 'LinkedIn'; - async refreshToken(refresh_token: string): Promise<AuthTokenDetails> { - const {access_token: accessToken, refresh_token: refreshToken} = await (await fetch('https://www.linkedin.com/oauth/v2/accessToken', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' + identifier = 'linkedin'; + name = 'LinkedIn'; + async refreshToken(refresh_token: string): Promise<AuthTokenDetails> { + const { access_token: accessToken, refresh_token: refreshToken } = await ( + await fetch('https://www.linkedin.com/oauth/v2/accessToken', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token, + client_id: process.env.LINKEDIN_CLIENT_ID!, + client_secret: process.env.LINKEDIN_CLIENT_SECRET!, + }), + }) + ).json(); + + const { + name, + sub: id, + picture, + } = await ( + await fetch('https://api.linkedin.com/v2/userinfo', { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) + ).json(); + + return { + id, + accessToken, + refreshToken, + name, + picture, + }; + } + + async generateAuthUrl() { + const state = makeId(6); + const codeVerifier = makeId(30); + const url = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${ + process.env.LINKEDIN_CLIENT_ID + }&redirect_uri=${encodeURIComponent( + `${process.env.FRONTEND_URL}/integrations/social/linkedin` + )}&state=${state}&scope=${encodeURIComponent( + 'openid profile w_member_social r_liteprofile' + )}`; + return { + url, + codeVerifier, + state, + }; + } + + async authenticate(params: { code: string; codeVerifier: string }) { + const body = new URLSearchParams(); + body.append('grant_type', 'authorization_code'); + body.append('code', params.code); + body.append( + 'redirect_uri', + `${process.env.FRONTEND_URL}/integrations/social/linkedin` + ); + body.append('client_id', process.env.LINKEDIN_CLIENT_ID!); + body.append('client_secret', process.env.LINKEDIN_CLIENT_SECRET!); + + const { + access_token: accessToken, + expires_in: expiresIn, + refresh_token: refreshToken, + } = await ( + await fetch('https://www.linkedin.com/oauth/v2/accessToken', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body, + }) + ).json(); + + const { + name, + sub: id, + picture, + } = await ( + await fetch('https://api.linkedin.com/v2/userinfo', { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) + ).json(); + + return { + id, + accessToken, + refreshToken, + expiresIn, + name, + picture, + }; + } + + async post( + id: string, + accessToken: string, + postDetails: PostDetails[] + ): Promise<PostResponse[]> { + const [firstPost, ...restPosts] = postDetails; + console.log('posting'); + const data = await fetch('https://api.linkedin.com/v2/posts', { + method: 'POST', + headers: { + 'X-Restli-Protocol-Version': '2.0.0', + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify({ + author: `urn:li:person:${id}`, + commentary: firstPost.message, + visibility: 'PUBLIC', + distribution: { + feedDistribution: 'MAIN_FEED', + targetEntities: [], + thirdPartyDistributionChannels: [], + }, + lifecycleState: 'PUBLISHED', + isReshareDisabledByAuthor: false, + // content: { + // // contentEntities: [ + // // { + // // entityLocation: 'URL_OF_THE_CONTENT_TO_SHARE', + // // thumbnails: [ + // // { + // // resolvedUrl: 'URL_OF_THE_THUMBNAIL_IMAGE', + // // }, + // // ], + // // }, + // // ], + // title: firstPost.message, + // }, + // distribution: { + // linkedInDistributionTarget: {}, + // }, + // owner: `urn:li:person:${id}`, + // subject: firstPost.message, + // text: { + // text: firstPost.message, + // }, + }), + }); + + const topPostId = data.headers.get('x-restli-id')!; + const ids = [ + { + status: 'posted', + postId: topPostId, + id: firstPost.id, + releaseURL: `https://www.linkedin.com/feed/update/${topPostId}`, + }, + ]; + for (const post of restPosts) { + const {object} = await (await fetch( + `https://api.linkedin.com/v2/socialActions/${decodeURIComponent( + topPostId + )}/comments`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify({ + actor: `urn:li:person:${id}`, + object: topPostId, + message: { + text: post.message, }, - body: new URLSearchParams({ - grant_type: 'refresh_token', - refresh_token, - client_id: process.env.LINKEDIN_CLIENT_ID!, - client_secret: process.env.LINKEDIN_CLIENT_SECRET! - }) - })).json() - - const {id, localizedFirstName, localizedLastName} = await (await fetch('https://api.linkedin.com/v2/me', { - headers: { - Authorization: `Bearer ${accessToken}` - } - })).json(); - - return { - id, - accessToken, - refreshToken, - name: `${localizedFirstName} ${localizedLastName}` + }), } + )).json() + + ids.push({ + status: 'posted', + postId: object, + id: post.id, + releaseURL: `https://www.linkedin.com/embed/feed/update/${object}`, + }); } - async generateAuthUrl() { - const state = makeId(6); - const codeVerifier = makeId(30); - const url = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${process.env.LINKEDIN_CLIENT_ID}&redirect_uri=${encodeURIComponent(`${process.env.FRONTEND_URL}/integrations/social/linkedin`)}&state=${state}&scope=${encodeURIComponent('openid profile w_member_social')}`; - return { - url, - codeVerifier, - state - } - } - - async authenticate(params: {code: string, codeVerifier: string}) { - const body = new URLSearchParams(); - body.append('grant_type', 'authorization_code'); - body.append('code', params.code); - body.append('redirect_uri', `${process.env.FRONTEND_URL}/integrations/social/linkedin`); - body.append('client_id', process.env.LINKEDIN_CLIENT_ID!); - body.append('client_secret', process.env.LINKEDIN_CLIENT_SECRET!); - - const {access_token: accessToken, expires_in: expiresIn, refresh_token: refreshToken, ...data} = await (await fetch('https://www.linkedin.com/oauth/v2/accessToken', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body - })).json() - - console.log({accessToken, expiresIn, refreshToken, data}); - - const {name, sub: id} = await (await fetch('https://api.linkedin.com/v2/userinfo', { - headers: { - Authorization: `Bearer ${accessToken}` - } - })).json(); - - return { - id, - accessToken, - refreshToken, - expiresIn, - name - } - } - - async schedulePost(accessToken: string, postDetails: PostDetails[]): Promise<PostResponse[]> { - return []; - } -} \ No newline at end of file + return ids; + } +} diff --git a/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts b/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts index 83fe495b..b04aea39 100644 --- a/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts @@ -17,7 +17,7 @@ export class RedditProvider implements SocialProvider { }) })).json(); - const {name, id} = await (await fetch('https://oauth.reddit.com/api/v1/me', { + const {name, id, icon_img} = await (await fetch('https://oauth.reddit.com/api/v1/me', { headers: { Authorization: `Bearer ${accessToken}` } @@ -28,7 +28,8 @@ export class RedditProvider implements SocialProvider { name, accessToken, refreshToken: newRefreshToken, - expiresIn + expiresIn, + picture: icon_img.split('?')[0] } } @@ -57,7 +58,7 @@ export class RedditProvider implements SocialProvider { }) })).json(); - const {name, id} = await (await fetch('https://oauth.reddit.com/api/v1/me', { + const {name, id, icon_img} = await (await fetch('https://oauth.reddit.com/api/v1/me', { headers: { Authorization: `Bearer ${accessToken}` } @@ -68,14 +69,28 @@ export class RedditProvider implements SocialProvider { name, accessToken, refreshToken, - expiresIn + expiresIn, + picture: icon_img.split('?')[0] } } - async schedulePost(accessToken: string, postDetails: PostDetails[]): Promise<PostResponse[]> { - return [{ - postId: '123', - status: 'scheduled' - }]; + async post(id: string, accessToken: string, postDetails: PostDetails[]): Promise<PostResponse[]> { + const [post, ...rest] = postDetails; + const response = await fetch('https://oauth.reddit.com/api/submit', { + method: 'POST', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: new URLSearchParams({ + title: 'test', + kind: 'self', + text: post.message, + sr: '/r/gitroom' + }) + }); + + console.log(response); + return []; } } \ No newline at end of file diff --git a/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts b/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts index 13b62f9c..aa43b38b 100644 --- a/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts +++ b/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts @@ -16,22 +16,26 @@ export type AuthTokenDetails = { accessToken: string; // The obtained access token refreshToken?: string; // The refresh token, if applicable expiresIn?: number; // The duration in seconds for which the access token is valid + picture?: string; }; export interface ISocialMediaIntegration { - schedulePost(accessToken: string, postDetails: PostDetails[]): Promise<PostResponse[]>; // Schedules a new post + post(id: string, accessToken: string, postDetails: PostDetails[]): Promise<PostResponse[]>; // Schedules a new post } export type PostResponse = { + id: string; // The db internal id of the post postId: string; // The ID of the scheduled post returned by the platform + releaseURL: string; // The URL of the post on the platform status: string; // Status of the operation or initial post status }; export type PostDetails = { + id: string; message: string; - scheduledTime: Date; // The time when the post should be published - media?: MediaContent[]; // Optional array of media content to be attached with the post - poll?: PollDetails; // Optional poll details + settings: object; + media?: MediaContent[]; + poll?: PollDetails; }; export type PollDetails = { diff --git a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts index a7031a18..43da34d3 100644 --- a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts @@ -1,67 +1,105 @@ import { TwitterApi } from 'twitter-api-v2'; -import {AuthTokenDetails, PostDetails, PostResponse, SocialProvider} from "@gitroom/nestjs-libraries/integrations/social/social.integrations.interface"; +import { + AuthTokenDetails, + PostDetails, + PostResponse, + SocialProvider, +} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface'; export class XProvider implements SocialProvider { - identifier = 'x'; - name = 'X'; - async refreshToken(refreshToken: string): Promise<AuthTokenDetails> { - const startingClient = new TwitterApi({ clientId: process.env.TWITTER_CLIENT_ID!, clientSecret: process.env.TWITTER_CLIENT_SECRET! }); - const { accessToken, refreshToken: newRefreshToken, expiresIn, client } = await startingClient.refreshOAuth2Token(refreshToken); - const {data: {id, name}} = await client.v2.me(); - return { - id, - name, - accessToken, - refreshToken: newRefreshToken, - expiresIn - } + identifier = 'x'; + name = 'X'; + async refreshToken(refreshToken: string): Promise<AuthTokenDetails> { + const startingClient = new TwitterApi({ + clientId: process.env.TWITTER_CLIENT_ID!, + clientSecret: process.env.TWITTER_CLIENT_SECRET!, + }); + const { + accessToken, + refreshToken: newRefreshToken, + expiresIn, + client, + } = await startingClient.refreshOAuth2Token(refreshToken); + const { + data: { id, name, profile_image_url }, + } = await client.v2.me(); + return { + id, + name, + accessToken, + refreshToken: newRefreshToken, + expiresIn, + picture: profile_image_url, + }; + } + + async generateAuthUrl() { + const client = new TwitterApi({ + clientId: process.env.TWITTER_CLIENT_ID!, + clientSecret: process.env.TWITTER_CLIENT_SECRET!, + }); + const { url, codeVerifier, state } = client.generateOAuth2AuthLink( + process.env.FRONTEND_URL + '/integrations/social/x', + { scope: ['tweet.read', 'users.read', 'tweet.write', 'offline.access'] } + ); + return { + url, + codeVerifier, + state, + }; + } + + async authenticate(params: { code: string; codeVerifier: string }) { + const startingClient = new TwitterApi({ + clientId: process.env.TWITTER_CLIENT_ID!, + clientSecret: process.env.TWITTER_CLIENT_SECRET!, + }); + const { accessToken, refreshToken, expiresIn, client } = + await startingClient.loginWithOAuth2({ + code: params.code, + codeVerifier: params.codeVerifier, + redirectUri: process.env.FRONTEND_URL + '/integrations/social/x', + }); + + const { + data: { id, name, profile_image_url }, + } = await client.v2.me({ + 'user.fields': 'profile_image_url', + }); + + return { + id, + accessToken, + name, + refreshToken, + expiresIn, + picture: profile_image_url, + }; + } + + async post( + id: string, + accessToken: string, + postDetails: PostDetails[], + ): Promise<PostResponse[]> { + const client = new TwitterApi(accessToken); + const {data: {username}} = await client.v2.me({ + "user.fields": "username" + }); + const ids: Array<{postId: string, id: string, releaseURL: string}> = []; + for (const post of postDetails) { + const { data }: { data: { id: string } } = await client.v2.tweet({ + text: post.message, + ...(ids.length + ? { reply: { in_reply_to_tweet_id: ids[ids.length - 1].postId } } + : {}), + }); + ids.push({postId: data.id, id: post.id, releaseURL: `https://twitter.com/${username}/status/${data.id}`}); } - async generateAuthUrl() { - const client = new TwitterApi({ clientId: process.env.TWITTER_CLIENT_ID!, clientSecret: process.env.TWITTER_CLIENT_SECRET! }); - const {url, codeVerifier, state} = client.generateOAuth2AuthLink( - process.env.FRONTEND_URL + '/integrations/social/x', - { scope: ['tweet.read', 'users.read', 'tweet.write', 'offline.access'] }); - return { - url, - codeVerifier, - state - } - } - - async authenticate(params: {code: string, codeVerifier: string}) { - const startingClient = new TwitterApi({ clientId: process.env.TWITTER_CLIENT_ID!, clientSecret: process.env.TWITTER_CLIENT_SECRET! }); - const {accessToken, refreshToken, expiresIn, client} = await startingClient.loginWithOAuth2({ - code: params.code, - codeVerifier: params.codeVerifier, - redirectUri: process.env.FRONTEND_URL + '/integrations/social/x' - }); - - const {data: {id, name}} = await client.v2.me(); - - return { - id, - accessToken, - name, - refreshToken, - expiresIn - } - } - - async schedulePost(accessToken: string, postDetails: PostDetails[]): Promise<PostResponse[]> { - const client = new TwitterApi(accessToken); - const ids: string[] = []; - for (const post of postDetails) { - const {data}: {data: {id: string}} = await client.v2.tweet({ - text: post.message, - ...ids.length ? { reply: {in_reply_to_tweet_id: ids[ids.length - 1]} } : {}, - }); - ids.push(data.id); - } - - return ids.map(p => ({ - postId: p, - status: 'posted' - })); - } -} \ No newline at end of file + return ids.map((p) => ({ + ...p, + status: 'posted', + })); + } +} diff --git a/libraries/react-shared-libraries/src/form/button.tsx b/libraries/react-shared-libraries/src/form/button.tsx index 03cb734a..38368c8f 100644 --- a/libraries/react-shared-libraries/src/form/button.tsx +++ b/libraries/react-shared-libraries/src/form/button.tsx @@ -1,8 +1,8 @@ import {ButtonHTMLAttributes, DetailedHTMLProps, FC} from "react"; import {clsx} from "clsx"; -export const Button: FC<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>> = (props) => { +export const Button: FC<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> & {secondary?: boolean}> = (props) => { return ( - <button {...props} type={props.type || 'button'} className={clsx('bg-forth px-[24px] h-[40px] cursor-pointer items-center justify-center flex', props?.className)} /> + <button {...props} type={props.type || 'button'} className={clsx(`${props.secondary ? 'bg-sixth' : 'bg-forth'} px-[24px] h-[40px] cursor-pointer items-center justify-center flex`, props?.className)} /> ) } \ No newline at end of file diff --git a/libraries/react-shared-libraries/src/form/input.tsx b/libraries/react-shared-libraries/src/form/input.tsx new file mode 100644 index 00000000..5b3e578c --- /dev/null +++ b/libraries/react-shared-libraries/src/form/input.tsx @@ -0,0 +1,22 @@ +"use client"; + +import {DetailedHTMLProps, FC, InputHTMLAttributes, useMemo} from "react"; +import clsx from "clsx"; +import {useFormContext} from "react-hook-form"; + +export const Input: FC<DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> & {label: string, name: string}> = (props) => { + const {label, className, ...rest} = props; + const form = useFormContext(); + const err = useMemo(() => { + if (!form || !form.formState.errors[props?.name!]) return; + return form?.formState?.errors?.[props?.name!]?.message! as string; + }, [form?.formState?.errors?.[props?.name!]?.message]); + + return ( + <div className="flex flex-col gap-[6px]"> + <div className="font-['Inter'] text-[14px]">{label}</div> + <input {...form.register(props.name)} className={clsx("bg-input h-[44px] px-[16px] outline-none border-fifth border rounded-[4px] text-inputText placeholder-inputText", className)} {...rest} /> + <div className="text-red-400 text-[12px]">{err || <> </>}</div> + </div> + ) +} \ No newline at end of file diff --git a/libraries/react-shared-libraries/src/helpers/delete.dialog.tsx b/libraries/react-shared-libraries/src/helpers/delete.dialog.tsx new file mode 100644 index 00000000..26aa59e1 --- /dev/null +++ b/libraries/react-shared-libraries/src/helpers/delete.dialog.tsx @@ -0,0 +1,14 @@ +import Swal from "sweetalert2"; + +export const deleteDialog = async (message: string, confirmButton?: string) => { + const fire = await Swal.fire({ + title: 'Are you sure?', + text: message, + icon: 'warning', + showCancelButton: true, + confirmButtonText: confirmButton || 'Yes, delete it!', + cancelButtonText: 'No, cancel!', + }); + + return fire.isConfirmed; +} diff --git a/libraries/react-shared-libraries/src/helpers/mantine.wrapper.tsx b/libraries/react-shared-libraries/src/helpers/mantine.wrapper.tsx new file mode 100644 index 00000000..5cb14ca0 --- /dev/null +++ b/libraries/react-shared-libraries/src/helpers/mantine.wrapper.tsx @@ -0,0 +1,19 @@ +"use client"; +import {ReactNode} from "react"; +import {MantineProvider} from "@mantine/core"; +import {ModalsProvider} from "@mantine/modals"; + +export const MantineWrapper = (props: { children: ReactNode }) => { + return ( + <MantineProvider> + <ModalsProvider modalProps={{ + classNames: { + modal: 'bg-primary text-white border-fifth border', + close: 'bg-black hover:bg-black cursor-pointer', + } + }}> + {props.children} + </ModalsProvider> + </MantineProvider> + ) +}; diff --git a/libraries/react-shared-libraries/src/helpers/use.prevent.window.unload.tsx b/libraries/react-shared-libraries/src/helpers/use.prevent.window.unload.tsx new file mode 100644 index 00000000..ef4eba61 --- /dev/null +++ b/libraries/react-shared-libraries/src/helpers/use.prevent.window.unload.tsx @@ -0,0 +1,10 @@ +import {useEffect} from "react"; + +export const usePreventWindowUnload = (preventDefault: boolean) => { + useEffect(() => { + if (!preventDefault) return; + const handleBeforeUnload = (event: any) => event.preventDefault(); + window.addEventListener("beforeunload", handleBeforeUnload); + return () => window.removeEventListener("beforeunload", handleBeforeUnload); + }, [preventDefault]); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8df866c9..829b7d6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,9 @@ ], "dependencies": { "@casl/ability": "^6.5.0", + "@hookform/resolvers": "^3.3.4", + "@mantine/core": "^5.10.5", + "@mantine/modals": "^5.10.5", "@nestjs/common": "^10.0.2", "@nestjs/core": "^10.0.2", "@nestjs/microservices": "^10.3.1", @@ -22,11 +25,14 @@ "@novu/notification-center": "^0.23.0", "@prisma/client": "^5.8.1", "@swc/helpers": "~0.5.2", + "@sweetalert2/theme-dark": "^5.0.16", "@types/bcrypt": "^5.0.2", "@types/jsonwebtoken": "^9.0.5", "@types/lodash": "^4.14.202", "@types/md5": "^2.3.5", + "@types/remove-markdown": "^0.3.4", "@types/stripe": "^8.0.417", + "@uiw/react-md-editor": "^4.0.3", "axios": "^1.0.0", "bcrypt": "^5.1.1", "bullmq": "^5.1.5", @@ -45,14 +51,16 @@ "prisma-paginate": "^5.2.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-hook-form": "^7.49.3", + "react-hook-form": "^7.50.1", "react-query": "^3.39.3", "react-router-dom": "6.11.2", "redis": "^4.6.12", "reflect-metadata": "^0.1.13", + "remove-markdown": "^0.5.0", "rxjs": "^7.8.0", "simple-statistics": "^7.8.3", "stripe": "^14.14.0", + "sweetalert2": "^11.10.5", "tslib": "^2.3.0", "twitter-api-v2": "^1.16.0", "yargs": "^17.7.2" @@ -107,6 +115,7 @@ "prisma": "^5.8.1", "react-refresh": "^0.10.0", "sass": "1.62.1", + "tailwind-scrollbar": "^3.1.0", "tailwindcss": "^3.4.1", "ts-jest": "^29.1.0", "ts-node": "10.9.1", @@ -3077,6 +3086,14 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" }, + "node_modules/@hookform/resolvers": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz", + "integrity": "sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -3708,6 +3725,20 @@ "react": ">=16.8.0" } }, + "node_modules/@mantine/modals": { + "version": "5.10.5", + "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-5.10.5.tgz", + "integrity": "sha512-q3BCqAxulcIZCL48vUrwSaXDhxDng/2daVky8K1mTPYNlcm9iN1mqVTUC4uFWhn4b2UmPu4UdbNePEgLuhK4Mw==", + "dependencies": { + "@mantine/utils": "5.10.5" + }, + "peerDependencies": { + "@mantine/core": "5.10.5", + "@mantine/hooks": "5.10.5", + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@mantine/styles": { "version": "5.10.5", "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-5.10.5.tgz", @@ -6329,6 +6360,11 @@ "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", "dev": true }, + "node_modules/@sweetalert2/theme-dark": { + "version": "5.0.16", + "resolved": "https://registry.npmjs.org/@sweetalert2/theme-dark/-/theme-dark-5.0.16.tgz", + "integrity": "sha512-SFXEVfp9jmaBLhYEeRilrcpQP/sQimfjtZdvazJ+l5jEdNVKDkPD/CD3T7V8VqUqk/q2zUBSvr0WOMrHoAvfCA==" + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -6622,6 +6658,14 @@ "@types/express": "*" } }, + "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==", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/eslint": { "version": "8.56.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", @@ -6645,8 +6689,15 @@ "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.4.tgz", + "integrity": "sha512-5idy3hvI9lAMqsyilBM+N+boaCf1MgoefbDxN6KEO5aK17TOHwFAYT9sjxzeKAiIWRUBgLxmZ9mPcnzZXtTcRQ==", + "dependencies": { + "@types/estree": "*" + } }, "node_modules/@types/express": { "version": "4.17.21", @@ -6681,6 +6732,14 @@ "@types/node": "*" } }, + "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==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -6791,12 +6850,25 @@ "resolved": "https://registry.npmjs.org/@types/md5/-/md5-2.3.5.tgz", "integrity": "sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw==" }, + "node_modules/@types/mdast": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", + "integrity": "sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, "node_modules/@types/node": { "version": "18.16.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.9.tgz", @@ -6816,11 +6888,15 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, + "node_modules/@types/prismjs": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.3.tgz", + "integrity": "sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==" + }, "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", - "dev": true + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/qs": { "version": "6.9.11", @@ -6838,7 +6914,6 @@ "version": "18.2.33", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.33.tgz", "integrity": "sha512-v+I7S+hu3PIBoVkKGpSYYpiBT1ijqEzWpzQD62/jm4K74hPpSP7FF9BnKG6+fg2+62weJYkkBWDJlZt5JO/9hg==", - "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -6854,6 +6929,11 @@ "@types/react": "*" } }, + "node_modules/@types/remove-markdown": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@types/remove-markdown/-/remove-markdown-0.3.4.tgz", + "integrity": "sha512-i753EH/p02bw7bLlpfS/4CV1rdikbGiLabWyVsAvsFid3cA5RNU1frG7JycgY+NSnFwtoGlElvZVceCytecTDA==" + }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", @@ -6872,8 +6952,7 @@ "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "node_modules/@types/semver": { "version": "7.5.6", @@ -6941,6 +7020,11 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, + "node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, "node_modules/@types/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -7320,6 +7404,60 @@ "@ucast/mongo": "^2.4.0" } }, + "node_modules/@uiw/copy-to-clipboard": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@uiw/copy-to-clipboard/-/copy-to-clipboard-1.0.16.tgz", + "integrity": "sha512-IXR+N363nLTR3ilklmM+B0nk774jVE/muOrBYt4Rdww/Pf3uP9XHyv2x6YZrbDh29F7w9BkzQyB8QF6WDShmJA==" + }, + "node_modules/@uiw/react-markdown-preview": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@uiw/react-markdown-preview/-/react-markdown-preview-5.0.7.tgz", + "integrity": "sha512-EmNI3LPM5Ff5ikcHJHcoZW268gpeAUPISfIwQaPjjHf/ET4aHNyo8sFBGV0+ycAaS52fXl2cvF+k/JweuMVVeQ==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@uiw/copy-to-clipboard": "~1.0.12", + "react-markdown": "~9.0.1", + "rehype-attr": "~3.0.1", + "rehype-autolink-headings": "~7.1.0", + "rehype-ignore": "^2.0.0", + "rehype-prism-plus": "2.0.0", + "rehype-raw": "^7.0.0", + "rehype-rewrite": "~4.0.0", + "rehype-slug": "~6.0.0", + "remark-gfm": "~4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@uiw/react-md-editor": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@uiw/react-md-editor/-/react-md-editor-4.0.3.tgz", + "integrity": "sha512-TvChXxUBUS21Rk0cVC0aeJoWcFZ/G0xN/Hc4Lv9FGFK8wPOHESd7Bcq4jNRHJ6lEzE/+d4Wh00lEVNKj+rQyBw==", + "dependencies": { + "@babel/runtime": "^7.14.6", + "@uiw/react-markdown-preview": "^5.0.6", + "rehype": "~13.0.0", + "rehype-prism-plus": "~2.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, "node_modules/@vitejs/plugin-react": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", @@ -8564,6 +8702,15 @@ "@babel/core": "^7.0.0" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "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", @@ -8613,6 +8760,15 @@ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -8830,8 +8986,7 @@ "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -9203,6 +9358,15 @@ } ] }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", @@ -9245,6 +9409,42 @@ "node": ">=10" } }, + "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==", + "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==", + "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==", + "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==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", @@ -9525,6 +9725,15 @@ "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==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -10206,6 +10415,21 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-selector-parser": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.0.4.tgz", + "integrity": "sha512-pnmS1dbKsz6KA4EW4BznyPL2xxkNDRg62hcD0v8g6DEw2W7hxOln5M953jsp9hmw5Dg57S6o/A8GOn37mbAgcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, "node_modules/css-tree": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", @@ -10421,6 +10645,18 @@ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -10659,7 +10895,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "engines": { "node": ">=6" } @@ -10709,6 +10944,18 @@ "detect-port": "bin/detect-port.js" } }, + "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==", + "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", @@ -10745,6 +10992,18 @@ "node": ">=8" } }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -11038,7 +11297,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "engines": { "node": ">=0.12" }, @@ -11940,6 +12198,15 @@ "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==", + "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", @@ -12262,6 +12529,11 @@ "node": ">=4" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -12965,6 +13237,11 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" + }, "node_modules/glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", @@ -13238,6 +13515,248 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.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-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-heading-rank": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", + "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "dependencies": { + "@types/hast": "^3.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==", + "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==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.2.tgz", + "integrity": "sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==", + "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-select": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.2.tgz", + "integrity": "sha512-hT/SD/d/Meu+iobvgkffo1QecV8WeKWxwsNMzcTJsKw1cKTQKSR/7ArJeURLNJF9HDjp9nVoORyNNJxrvBye8Q==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "not": "^0.1.0", + "nth-check": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.0.tgz", + "integrity": "sha512-IVGhNgg7vANuUA2XKrT6sOIIPgaYZnmLx3l/CCOAK0PtgfoHrZwX7jCSYyFxHTrGmC6S9q8aQQekjp4JPZF+cw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^9.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", + "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==", + "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": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^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==", + "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-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.0.tgz", + "integrity": "sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==", + "dependencies": { + "@types/hast": "^3.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==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -13363,6 +13882,24 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-url-attributes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.0.tgz", + "integrity": "sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==", + "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==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -13726,6 +14263,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/inline-style-parser": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.2.tgz", + "integrity": "sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ==" + }, "node_modules/internal-slot": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", @@ -13771,6 +14313,28 @@ "node": ">= 0.10" } }, + "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==", + "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==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -13889,6 +14453,15 @@ "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==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -13942,6 +14515,15 @@ "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==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -15772,6 +16354,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "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", @@ -15878,6 +16469,15 @@ "tmpl": "1.0.5" } }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/match-sorter": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.3.tgz", @@ -15897,6 +16497,272 @@ "is-buffer": "~1.1.6" } }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "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==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz", + "integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==", + "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.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "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.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "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.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "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==", + "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==", + "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==", + "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.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", + "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==", + "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.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.0.tgz", + "integrity": "sha512-A8AJHlR7/wPQ3+Jre1+1rq040fX9A4Q1jG8JxmSNp/PLPHg80A6475wxTp3KzHpApFH6yWxFotHrJQA3dXP6/w==", + "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-remove-position": "^5.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==", + "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==", + "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.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz", + "integrity": "sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==", + "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.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "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-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==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -15951,6 +16817,541 @@ "node": ">= 0.6" } }, + "node_modules/micromark": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "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.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz", + "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "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==", + "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.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==", + "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.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==", + "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.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==", + "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.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz", + "integrity": "sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==", + "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==", + "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.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz", + "integrity": "sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==", + "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.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "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.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "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.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "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.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "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.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "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.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "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.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "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.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz", + "integrity": "sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "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.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -16555,6 +17956,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/not": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz", + "integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA==" + }, "node_modules/npm-package-arg": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", @@ -16597,7 +18003,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, "dependencies": { "boolbase": "^1.0.0" }, @@ -17023,6 +18428,30 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^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.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -17054,11 +18483,15 @@ "node": ">= 0.10" } }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dev": true, "dependencies": { "entities": "^4.4.0" }, @@ -18123,6 +19556,15 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/property-information": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.4.1.tgz", + "integrity": "sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -18312,12 +19754,11 @@ } }, "node_modules/react-hook-form": { - "version": "7.49.3", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.49.3.tgz", - "integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==", + "version": "7.50.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.50.1.tgz", + "integrity": "sha512-3PCY82oE0WgeOgUtIr3nYNNtNvqtJ7BZjsbxh6TnYNbXButaD5WpjOmTjdxZfheuHKR68qfeFnEDVYoSSFPMTQ==", "engines": { - "node": ">=18", - "pnpm": "8" + "node": ">=12.22.0" }, "funding": { "type": "opencollective", @@ -18344,6 +19785,31 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/react-markdown": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", + "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==", + "dependencies": { + "@types/hast": "^3.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-query": { "version": "3.39.3", "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", @@ -18511,6 +19977,62 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" }, + "node_modules/refractor": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz", + "integrity": "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prismjs": "^1.0.0", + "hastscript": "^7.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/refractor/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/refractor/node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/refractor/node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -18598,11 +20120,228 @@ "jsesc": "bin/jsesc" } }, + "node_modules/rehype": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.1.tgz", + "integrity": "sha512-AcSLS2mItY+0fYu9xKxOu1LhUZeBZZBx8//5HKzF+0XP+eP8+6a5MXn2+DW2kfXR6Dtp1FEXMVrjyKAcvcU8vg==", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-attr": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/rehype-attr/-/rehype-attr-3.0.3.tgz", + "integrity": "sha512-Up50Xfra8tyxnkJdCzLBIBtxOcB2M1xdeKe1324U06RAvSjYm7ULSeoM+b/nYPQPVd7jsXJ9+39IG1WAJPXONw==", + "dependencies": { + "unified": "~11.0.0", + "unist-util-visit": "~5.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/rehype-autolink-headings": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz", + "integrity": "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-ignore": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rehype-ignore/-/rehype-ignore-2.0.2.tgz", + "integrity": "sha512-BpAT/3lU9DMJ2siYVD/dSR0A/zQgD6Fb+fxkJd4j+wDVy6TYbYpK+FZqu8eM9EuNKGvi4BJR7XTZ/+zF02Dq8w==", + "dependencies": { + "hast-util-select": "^6.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.0.tgz", + "integrity": "sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-prism-plus": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-2.0.0.tgz", + "integrity": "sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ==", + "dependencies": { + "hast-util-to-string": "^3.0.0", + "parse-numeric-range": "^1.3.0", + "refractor": "^4.8.0", + "rehype-parse": "^9.0.0", + "unist-util-filter": "^5.0.0", + "unist-util-visit": "^5.0.0" + } + }, + "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==", + "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-rewrite": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rehype-rewrite/-/rehype-rewrite-4.0.2.tgz", + "integrity": "sha512-rjLJ3z6fIV11phwCqHp/KRo8xuUCO8o9bFJCNw5o6O2wlLk6g8r323aRswdGBQwfXPFYeSuZdAjp4tzo6RGqEg==", + "dependencies": { + "hast-util-select": "^6.0.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/rehype-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", + "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "dependencies": { + "@types/hast": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.0.tgz", + "integrity": "sha512-1TX1i048LooI9QoecrXy7nGFFbFSufxVRAfc6Y9YMRAi56l+oB0zP51mLSV312uRuvVLPV1opSlJmslozR1XHQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", + "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "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==", + "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.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz", + "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==", + "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==", + "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/remove-accents": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" }, + "node_modules/remove-markdown": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.5.0.tgz", + "integrity": "sha512-x917M80K97K5IN1L8lUvFehsfhR8cYjGQ/yAMRI9E7JIKivtl5Emo5iD13DhMr+VojzMCiYk8V2byNPwT/oapg==" + }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -19474,6 +21213,15 @@ "source-map": "^0.6.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==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -19696,6 +21444,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "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", @@ -19845,6 +21606,14 @@ "webpack": "^5.0.0" } }, + "node_modules/style-to-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.5.tgz", + "integrity": "sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ==", + "dependencies": { + "inline-style-parser": "0.2.2" + } + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -20130,6 +21899,15 @@ "url": "https://opencollective.com/svgo" } }, + "node_modules/sweetalert2": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.5.tgz", + "integrity": "sha512-q9eE3EKhMcpIDU/Xcz7z5lk8axCGkgxwK47gXGrrfncnBJWxHPPHnBVAjfsVXcTt8Yi8U6HNEcBRSu+qGeyFdA==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/limonte" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -20141,6 +21919,18 @@ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, + "node_modules/tailwind-scrollbar": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-3.1.0.tgz", + "integrity": "sha512-pmrtDIZeHyu2idTejfV59SbaJyvp1VRjYxAjZBH0jnyrPRo6HL1kD5Glz8VPagasqr6oAx6M05+Tuw429Z8jxg==", + "dev": true, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "tailwindcss": "3.x" + } + }, "node_modules/tailwindcss": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", @@ -20597,6 +22387,15 @@ "node": ">=14" } }, + "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==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trim-repeated": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-2.0.0.tgz", @@ -20621,6 +22420,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -21031,6 +22839,35 @@ "node": ">=4" } }, + "node_modules/unified": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz", + "integrity": "sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==", + "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/unified/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==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/union": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", @@ -21043,6 +22880,92 @@ "node": ">= 0.8.0" } }, + "node_modules/unist-util-filter": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-5.0.1.tgz", + "integrity": "sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "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==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.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==", + "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==", + "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.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -21266,6 +23189,46 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", + "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "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.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "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.0.12", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", @@ -21487,6 +23450,15 @@ "defaults": "^1.0.3" } }, + "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==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/webfontloader": { "version": "1.6.28", "resolved": "https://registry.npmjs.org/webfontloader/-/webfontloader-1.6.28.tgz", @@ -22156,6 +24128,15 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "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==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index e9f9a010..cb56d008 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "license": "MIT", "scripts": { - "dev": "concurrently \"stripe listen --forward-to localhost:3000/payment\" \"nx run-many --target=serve --projects=frontend,backend --parallel=4\"", + "dev": "concurrently \"stripe listen --forward-to localhost:3000/payment\" \"nx run-many --target=serve --projects=frontend,backend,workers --parallel=4\"", "workers": "nx run workers:serve:development", "cron": "nx run cron:serve:development", "command": "nx run commands:build && nx run commands:command", @@ -13,6 +13,9 @@ "private": true, "dependencies": { "@casl/ability": "^6.5.0", + "@hookform/resolvers": "^3.3.4", + "@mantine/core": "^5.10.5", + "@mantine/modals": "^5.10.5", "@nestjs/common": "^10.0.2", "@nestjs/core": "^10.0.2", "@nestjs/microservices": "^10.3.1", @@ -22,11 +25,14 @@ "@novu/notification-center": "^0.23.0", "@prisma/client": "^5.8.1", "@swc/helpers": "~0.5.2", + "@sweetalert2/theme-dark": "^5.0.16", "@types/bcrypt": "^5.0.2", "@types/jsonwebtoken": "^9.0.5", "@types/lodash": "^4.14.202", "@types/md5": "^2.3.5", + "@types/remove-markdown": "^0.3.4", "@types/stripe": "^8.0.417", + "@uiw/react-md-editor": "^4.0.3", "axios": "^1.0.0", "bcrypt": "^5.1.1", "bullmq": "^5.1.5", @@ -45,14 +51,16 @@ "prisma-paginate": "^5.2.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-hook-form": "^7.49.3", + "react-hook-form": "^7.50.1", "react-query": "^3.39.3", "react-router-dom": "6.11.2", "redis": "^4.6.12", "reflect-metadata": "^0.1.13", + "remove-markdown": "^0.5.0", "rxjs": "^7.8.0", "simple-statistics": "^7.8.3", "stripe": "^14.14.0", + "sweetalert2": "^11.10.5", "tslib": "^2.3.0", "twitter-api-v2": "^1.16.0", "yargs": "^17.7.2" @@ -107,6 +115,7 @@ "prisma": "^5.8.1", "react-refresh": "^0.10.0", "sass": "1.62.1", + "tailwind-scrollbar": "^3.1.0", "tailwindcss": "^3.4.1", "ts-jest": "^29.1.0", "ts-node": "10.9.1",