mirror of
https://github.com/ahmadk953/tasko.git
synced 2025-01-31 00:53:37 +00:00
Merge pull request #926 from ahmadk953/liveblocks
Implement basic Liveblocks integration and update README
This commit is contained in:
commit
6d14c8f43f
12 changed files with 336 additions and 15 deletions
23
README.md
23
README.md
|
@ -22,19 +22,20 @@ Documentation can be found [here](https://docs.tasko.ahmadk953.org/).
|
||||||
|
|
||||||
This will be published on the site some time soon but for now, the roadmap will be listed here.
|
This will be published on the site some time soon but for now, the roadmap will be listed here.
|
||||||
|
|
||||||
- [x] Finish adding started at date field
|
|
||||||
- [x] Pagination for Audit Logs page
|
|
||||||
- [ ] Board sorting options (Boards Page)
|
- [ ] Board sorting options (Boards Page)
|
||||||
- [ ] Add real-time collaboration
|
- [ ] Add real-time collaboration _In Progress - Liveblocks with presence implemented roughly_
|
||||||
- [ ] Add end-to-end Database encryption (for customer data such as card titles and descriptions, and subscription information)
|
- [ ] Add end-to-end Database encryption (for customer data such as card titles and descriptions, and subscription information)
|
||||||
- [ ] Add dark mode
|
- [ ] Add dark mode _In Progress_
|
||||||
- [ ] Markdown Support in Card Descriptions
|
- [ ] Rich Text Support in Card Descriptions
|
||||||
- [ ] Self-Hosted Version
|
- [ ] Self-Hosted Version
|
||||||
|
- [ ] Move Roadmap to Website _In Progress - Starting off With a Basic MDX Page_
|
||||||
|
|
||||||
## Contributing and Development
|
## Contributing and Development
|
||||||
|
|
||||||
### Development Commands
|
### Development Commands
|
||||||
|
|
||||||
|
Install Dependencies: ``yarn install --immutable``
|
||||||
|
|
||||||
Start Dev Server: ``yarn dev``
|
Start Dev Server: ``yarn dev``
|
||||||
|
|
||||||
Production Build: ``yarn build``
|
Production Build: ``yarn build``
|
||||||
|
@ -47,12 +48,16 @@ Check Formatting: ``yarn format``
|
||||||
|
|
||||||
Fix Formatting: ``yarn format:fix``
|
Fix Formatting: ``yarn format:fix``
|
||||||
|
|
||||||
Please make sure to lint and check formatting (using the commands listed above) before submitting a Pull Request.
|
Testing: ``yarn test``
|
||||||
|
|
||||||
|
Coverage: ``yarn coverage``
|
||||||
|
|
||||||
|
Please make sure to lint, check formatting, and test (using the commands listed above) before submitting a Pull Request.
|
||||||
|
|
||||||
## Legal
|
## Legal
|
||||||
|
|
||||||
Privacy Policy _Temporarily removed to revamp it._
|
Privacy Policy _Will be added back soon_
|
||||||
|
|
||||||
Terms of Service _The Terms of Service will be added soon!_
|
Terms of Service _Will be added along with privacy policy_
|
||||||
|
|
||||||
[License](https://github.com/ahmadk953/tasko/blob/main/LICENCE) _Will be located on website at some point in time._
|
[License](https://github.com/ahmadk953/tasko/blob/main/LICENCE) _Will be added onto the website with privacy policy and terms of service_
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ClientSideSuspense,
|
||||||
|
LiveblocksProvider,
|
||||||
|
RoomProvider,
|
||||||
|
} from '@liveblocks/react';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
|
||||||
|
export const BoardLiveblocks = ({
|
||||||
|
children,
|
||||||
|
boardId,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
boardId: string;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<LiveblocksProvider
|
||||||
|
authEndpoint={async (room) => {
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
BoardId: `${boardId}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const body = JSON.stringify({
|
||||||
|
room,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch('/api/liveblocks-auth', {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}}
|
||||||
|
throttle={16}
|
||||||
|
>
|
||||||
|
<RoomProvider id={`${boardId}`} initialPresence={{ cursor: null }}>
|
||||||
|
<ClientSideSuspense fallback={<BoardLiveblocks.Skeleton />}>
|
||||||
|
{children}
|
||||||
|
</ClientSideSuspense>
|
||||||
|
</RoomProvider>
|
||||||
|
</LiveblocksProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
BoardLiveblocks.Skeleton = function SkeletonBoardLiveblocks() {
|
||||||
|
return (
|
||||||
|
<div className='h-full overflow-x-auto p-4'>
|
||||||
|
<ol className='flex h-full gap-x-3'>
|
||||||
|
<li className='h-full w-[272px] shrink-0 select-none'>
|
||||||
|
<div className='w-full rounded-md bg-[#f1f2f4] pb-2 shadow-md'>
|
||||||
|
<div className='flex items-start justify-between gap-x-2 px-2 pt-2 text-sm font-semibold'>
|
||||||
|
<Skeleton className='h-7 truncate border-transparent bg-transparent px-[7px] py-1 text-sm font-medium' />
|
||||||
|
</div>
|
||||||
|
<ol className='mx-1 mt-2 flex flex-col gap-y-2 px-1 py-0.5'>
|
||||||
|
<Skeleton className='h-12 space-y-2 truncate rounded-md border-2 border-transparent bg-white px-3 py-2 text-sm shadow-sm' />
|
||||||
|
<Skeleton className='h-24 space-y-2 truncate rounded-md border-2 border-transparent bg-white px-3 py-2 text-sm shadow-sm' />
|
||||||
|
<Skeleton className='h-16 space-y-2 truncate rounded-md border-2 border-transparent bg-white px-3 py-2 text-sm shadow-sm' />
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<div className='w-1 flex-shrink-0' />
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -7,7 +7,7 @@ interface BoardNavbarProps {
|
||||||
data: Board;
|
data: Board;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BoardNavbar = async ({ data }: BoardNavbarProps) => {
|
export const BoardNavbar = ({ data }: BoardNavbarProps) => {
|
||||||
return (
|
return (
|
||||||
<div className='fixed top-14 z-[40] flex h-14 w-full items-center gap-x-4 bg-black/50 px-6 text-white'>
|
<div className='fixed top-14 z-[40] flex h-14 w-full items-center gap-x-4 bg-black/50 px-6 text-white'>
|
||||||
<BoardTitleForm data={data} />
|
<BoardTitleForm data={data} />
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useOthers, useMyPresence } from '@liveblocks/react/suspense';
|
||||||
|
import { Cursor } from './cursor';
|
||||||
|
import { colors } from '@/constants/colors';
|
||||||
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
export const BoardRoomWrapper = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
const others = useOthers();
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [numbers, setNumbers] = useState<number[]>([]);
|
||||||
|
const [width, setWidth] = useState(0);
|
||||||
|
const [height, setHeight] = useState(0);
|
||||||
|
const [myPresence, updateMyPresence] = useMyPresence();
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
setWidth(ref.current.clientWidth);
|
||||||
|
setHeight(ref.current.clientHeight);
|
||||||
|
}
|
||||||
|
}, [numbers]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleWindowResize() {
|
||||||
|
if (ref.current) {
|
||||||
|
setWidth(ref.current.clientWidth);
|
||||||
|
setHeight(ref.current.clientHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleWindowResize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleWindowResize);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function handlePointerMove(e: React.PointerEvent<HTMLDivElement>) {
|
||||||
|
if (!ref.current) return;
|
||||||
|
const rect = ref.current.getBoundingClientRect();
|
||||||
|
const normalizedCursorX = (e.clientX - rect.left) / rect.width;
|
||||||
|
const normalizedCursorY = (e.clientY - rect.top) / rect.height;
|
||||||
|
updateMyPresence({
|
||||||
|
cursor: { x: normalizedCursorX, y: normalizedCursorY },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePointerLeave(e: React.PointerEvent<HTMLDivElement>) {
|
||||||
|
updateMyPresence({ cursor: null });
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='relative h-full w-full'
|
||||||
|
onPointerMove={handlePointerMove}
|
||||||
|
onPointerLeave={handlePointerLeave}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{others.map(({ connectionId, presence }) => (
|
||||||
|
<Cursor
|
||||||
|
key={connectionId}
|
||||||
|
x={(presence.cursor?.x as number) * width}
|
||||||
|
y={(presence.cursor?.y as number) * height}
|
||||||
|
color={colors[connectionId % colors.length]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
interface CursorProps {
|
||||||
|
x: number | undefined;
|
||||||
|
y: number | undefined;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Cursor({ x, y, color }: CursorProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
transform: `translateX(${x}px) translateY(${y}px)`,
|
||||||
|
}}
|
||||||
|
width='16'
|
||||||
|
height='16'
|
||||||
|
viewBox='0 0 16 16'
|
||||||
|
fill='none'
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d='m13.67 6.03-11-4a.5.5 0 0 0-.64.64l4 11a.5.5 0 0 0 .935.015l1.92-4.8 4.8-1.92a.5.5 0 0 0 0-.935h-.015Z'
|
||||||
|
fill={`${color}`}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,8 +1,15 @@
|
||||||
import { auth } from '@clerk/nextjs/server';
|
import { auth } from '@clerk/nextjs/server';
|
||||||
import { notFound, redirect } from 'next/navigation';
|
import { notFound, redirect } from 'next/navigation';
|
||||||
|
import {
|
||||||
|
LiveblocksProvider,
|
||||||
|
RoomProvider,
|
||||||
|
ClientSideSuspense,
|
||||||
|
} from '@liveblocks/react/suspense';
|
||||||
|
|
||||||
import { db } from '@/lib/db';
|
import { db } from '@/lib/db';
|
||||||
import { BoardNavbar } from './_components/board-navbar';
|
import { BoardNavbar } from './_components/board-navbar';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { BoardLiveblocks } from './_components/board-liveblocks';
|
||||||
|
|
||||||
export async function generateMetadata(props: {
|
export async function generateMetadata(props: {
|
||||||
params: Promise<{ boardId: string }>;
|
params: Promise<{ boardId: string }>;
|
||||||
|
@ -53,7 +60,9 @@ const BoardIdLayout = async (props: {
|
||||||
>
|
>
|
||||||
<BoardNavbar data={board} />
|
<BoardNavbar data={board} />
|
||||||
<div className='absolute inset-0 bg-black/10' />
|
<div className='absolute inset-0 bg-black/10' />
|
||||||
<main className='relative h-full pt-28'>{children}</main>
|
<main className='relative h-full pt-28'>
|
||||||
|
<BoardLiveblocks boardId={params.boardId}>{children}</BoardLiveblocks>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
import { db } from '@/lib/db';
|
import { db } from '@/lib/db';
|
||||||
import { ListContainer } from './_components/list-container';
|
import { ListContainer } from './_components/list-container';
|
||||||
|
import { BoardRoomWrapper } from './_components/board-room-wrapper';
|
||||||
|
|
||||||
interface BoardIdPageProps {
|
interface BoardIdPageProps {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
|
@ -38,9 +39,11 @@ const BoardIdPage = async (props: BoardIdPageProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-full overflow-x-auto p-4'>
|
<BoardRoomWrapper>
|
||||||
<ListContainer boardId={params.boardId} data={lists} />
|
<div className='h-full overflow-x-auto p-4'>
|
||||||
</div>
|
<ListContainer boardId={params.boardId} data={lists} />
|
||||||
|
</div>
|
||||||
|
</BoardRoomWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
39
app/api/liveblocks-auth/route.ts
Normal file
39
app/api/liveblocks-auth/route.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { db } from '@/lib/db';
|
||||||
|
|
||||||
|
import { auth, currentUser } from '@clerk/nextjs/server';
|
||||||
|
import { Liveblocks } from '@liveblocks/node';
|
||||||
|
import { headers } from 'next/headers';
|
||||||
|
|
||||||
|
export async function POST(req: Request) {
|
||||||
|
const { sessionClaims } = await auth();
|
||||||
|
const user = await currentUser();
|
||||||
|
if (!sessionClaims || !user) {
|
||||||
|
return new Response('Not authorized', { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { room } = await req.json();
|
||||||
|
const boardId = (await headers()).get('BoardId') as string;
|
||||||
|
const board = await db.board.findUnique({
|
||||||
|
where: {
|
||||||
|
id: boardId,
|
||||||
|
orgId: sessionClaims.org_id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!board || board.orgId !== sessionClaims.org_id) {
|
||||||
|
return new Response('Not authorized', { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const liveblocks = new Liveblocks({
|
||||||
|
secret: process.env.LIVEBLOCKS_SECRET_KEY!,
|
||||||
|
});
|
||||||
|
const session = liveblocks.prepareSession(user.id, {
|
||||||
|
userInfo: {
|
||||||
|
name: user.fullName!,
|
||||||
|
avatar: user.imageUrl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
session.allow(room, session.FULL_ACCESS);
|
||||||
|
const { body, status } = await session.authorize();
|
||||||
|
|
||||||
|
return new Response(body, { status });
|
||||||
|
}
|
10
constants/colors.ts
Normal file
10
constants/colors.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export const colors = [
|
||||||
|
'#E57373',
|
||||||
|
'#9575CD',
|
||||||
|
'#4FC3F7',
|
||||||
|
'#81C784',
|
||||||
|
'#FFF176',
|
||||||
|
'#FF8A65',
|
||||||
|
'#F06292',
|
||||||
|
'#7986CB',
|
||||||
|
];
|
25
liveblocks.config.ts
Normal file
25
liveblocks.config.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
declare global {
|
||||||
|
interface Liveblocks {
|
||||||
|
Presence: {
|
||||||
|
cursor: { x: number; y: number } | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
Storage: {};
|
||||||
|
|
||||||
|
UserMeta: {
|
||||||
|
id: string;
|
||||||
|
info: {
|
||||||
|
name: string;
|
||||||
|
avatar: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
RoomEvent: {};
|
||||||
|
|
||||||
|
ThreadMetadata: {};
|
||||||
|
|
||||||
|
RoomInfo: {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
|
@ -19,6 +19,9 @@
|
||||||
"@arcjet/next": "^1.0.0-alpha.34",
|
"@arcjet/next": "^1.0.0-alpha.34",
|
||||||
"@clerk/nextjs": "^6.9.9",
|
"@clerk/nextjs": "^6.9.9",
|
||||||
"@hello-pangea/dnd": "^17.0.0",
|
"@hello-pangea/dnd": "^17.0.0",
|
||||||
|
"@liveblocks/client": "^2.15.1",
|
||||||
|
"@liveblocks/node": "^2.15.1",
|
||||||
|
"@liveblocks/react": "^2.15.1",
|
||||||
"@mdx-js/loader": "^3.1.0",
|
"@mdx-js/loader": "^3.1.0",
|
||||||
"@mdx-js/react": "^3.1.0",
|
"@mdx-js/react": "^3.1.0",
|
||||||
"@next/mdx": "^15.1.4",
|
"@next/mdx": "^15.1.4",
|
||||||
|
|
59
yarn.lock
59
yarn.lock
|
@ -1774,6 +1774,46 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@liveblocks/client@npm:2.15.2, @liveblocks/client@npm:^2.15.1":
|
||||||
|
version: 2.15.2
|
||||||
|
resolution: "@liveblocks/client@npm:2.15.2"
|
||||||
|
dependencies:
|
||||||
|
"@liveblocks/core": "npm:2.15.2"
|
||||||
|
checksum: 10c0/a9ea5ab865b0d1afada8670e3f10ab605f03da2e106e35dc40edbacec257462e723f48f1a0ce8cd1d74182b673fb934f13b14ad900805630072a0952f62d41c8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@liveblocks/core@npm:2.15.2":
|
||||||
|
version: 2.15.2
|
||||||
|
resolution: "@liveblocks/core@npm:2.15.2"
|
||||||
|
checksum: 10c0/eadef786899051ab6ceaa9f33020ef6f422bcc1abf4e504bbd8fb724d7ad4f3a97a4e9420cfc9b2988fa6ed71c17781a030dc69b81cb1d4420469d41d2b2fb48
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@liveblocks/node@npm:^2.15.1":
|
||||||
|
version: 2.15.2
|
||||||
|
resolution: "@liveblocks/node@npm:2.15.2"
|
||||||
|
dependencies:
|
||||||
|
"@liveblocks/core": "npm:2.15.2"
|
||||||
|
"@stablelib/base64": "npm:^1.0.1"
|
||||||
|
fast-sha256: "npm:^1.3.0"
|
||||||
|
node-fetch: "npm:^2.6.1"
|
||||||
|
checksum: 10c0/a5b13f2351218f1ba088c867963fd81669b3c13948651177f7186f84a7f66551246c7d8d002938a490c7a3e3ca2f4f2669c405c2141d92a5e64515166e8338b4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@liveblocks/react@npm:^2.15.1":
|
||||||
|
version: 2.15.2
|
||||||
|
resolution: "@liveblocks/react@npm:2.15.2"
|
||||||
|
dependencies:
|
||||||
|
"@liveblocks/client": "npm:2.15.2"
|
||||||
|
"@liveblocks/core": "npm:2.15.2"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
checksum: 10c0/ae1ba5473bd555e60e8c8ede1d62f38cdd029cc97f216c3bf765085f2c6f679ca61e5870b06407ac35be52e026af54b19bee7d3d9ccbfc49bde459a5c43e6e2f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@mdx-js/esbuild@npm:^3.0.0":
|
"@mdx-js/esbuild@npm:^3.0.0":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "@mdx-js/esbuild@npm:3.1.0"
|
resolution: "@mdx-js/esbuild@npm:3.1.0"
|
||||||
|
@ -3877,6 +3917,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@stablelib/base64@npm:^1.0.1":
|
||||||
|
version: 1.0.1
|
||||||
|
resolution: "@stablelib/base64@npm:1.0.1"
|
||||||
|
checksum: 10c0/6330720f021819d19cecfe274111b79a256caa81df478d6b0ae7effc8842b96915b6aeed85926ff05b4d48ec1fc78ad043d928b730ee4e6cc6e8cba6aa097bed
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@swc/counter@npm:0.1.3":
|
"@swc/counter@npm:0.1.3":
|
||||||
version: 0.1.3
|
version: 0.1.3
|
||||||
resolution: "@swc/counter@npm:0.1.3"
|
resolution: "@swc/counter@npm:0.1.3"
|
||||||
|
@ -7059,6 +7106,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"fast-sha256@npm:^1.3.0":
|
||||||
|
version: 1.3.0
|
||||||
|
resolution: "fast-sha256@npm:1.3.0"
|
||||||
|
checksum: 10c0/87f9e4baa7639576cf60a2b6235c9f436e1a1c52323abbd8a705b5bea8355500acf176f2aed0c14f2ecd6d6007e26151461bab2f27b8953bcca8d9d6b76a86e4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"fastq@npm:^1.6.0":
|
"fastq@npm:^1.6.0":
|
||||||
version: 1.18.0
|
version: 1.18.0
|
||||||
resolution: "fastq@npm:1.18.0"
|
resolution: "fastq@npm:1.18.0"
|
||||||
|
@ -10077,7 +10131,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"node-fetch@npm:^2.6.7":
|
"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7":
|
||||||
version: 2.7.0
|
version: 2.7.0
|
||||||
resolution: "node-fetch@npm:2.7.0"
|
resolution: "node-fetch@npm:2.7.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -12419,6 +12473,9 @@ __metadata:
|
||||||
"@eslint/eslintrc": "npm:^3.1.0"
|
"@eslint/eslintrc": "npm:^3.1.0"
|
||||||
"@eslint/js": "npm:^9.16.0"
|
"@eslint/js": "npm:^9.16.0"
|
||||||
"@hello-pangea/dnd": "npm:^17.0.0"
|
"@hello-pangea/dnd": "npm:^17.0.0"
|
||||||
|
"@liveblocks/client": "npm:^2.15.1"
|
||||||
|
"@liveblocks/node": "npm:^2.15.1"
|
||||||
|
"@liveblocks/react": "npm:^2.15.1"
|
||||||
"@mdx-js/loader": "npm:^3.1.0"
|
"@mdx-js/loader": "npm:^3.1.0"
|
||||||
"@mdx-js/react": "npm:^3.1.0"
|
"@mdx-js/react": "npm:^3.1.0"
|
||||||
"@microsoft/eslint-formatter-sarif": "npm:^3.1.0"
|
"@microsoft/eslint-formatter-sarif": "npm:^3.1.0"
|
||||||
|
|
Loading…
Reference in a new issue