Skip to content

Commit 666855f

Browse files
committed
v1
1 parent 0d50bdf commit 666855f

38 files changed

+1536
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# local env files
28+
.env*.local
29+
30+
# vercel
31+
.vercel
32+
33+
# typescript
34+
*.tsbuildinfo
35+
next-env.d.ts
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
public-hoist-pattern[]=*
2+
public-hoist-pattern[]=!tailwindcss
3+
shamefully-hoist=false
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# tiptap-demo-2
2+
3+
## Overview
4+
5+
This demo showcases **tiptap** (Library) for **text-editors** in **react**.
6+
7+
## Path
8+
9+
```
10+
apps/react/text-editors/libraries/tiptap/tiptap-demo-2/
11+
```
12+
13+
## Package Name
14+
15+
`@apps/react-text-editors-tiptap-tiptap-demo-2`
16+
17+
## Directory Structure
18+
19+
```
20+
tiptap-demo-2/
21+
├── app/
22+
│ ├── layout.tsx # Root layout
23+
│ └── page.tsx # Main page
24+
├── components/
25+
│ ├── header/ # Header components (Velt notifications, etc.)
26+
│ │ └── header.tsx
27+
│ ├── sidebar/ # Sidebar components
28+
│ │ └── sidebar.tsx
29+
│ └── document/ # Main document/canvas logic
30+
│ └── document-canvas.tsx
31+
├── hooks/ # Custom React hooks
32+
├── lib/ # Utility functions
33+
│ └── utils.ts
34+
├── public/ # Static assets
35+
├── styles/ # Global styles
36+
│ └── globals.css
37+
├── .npmrc # pnpm config to prevent Tailwind v4 hoisting
38+
├── next.config.js
39+
├── tailwind.config.js
40+
├── tsconfig.json
41+
├── components.json # shadcn/ui configuration
42+
└── package.json
43+
```
44+
45+
## Getting Started
46+
47+
### Install Dependencies
48+
49+
From the monorepo root:
50+
51+
```bash
52+
pnpm -w install
53+
```
54+
55+
### Run Development Server
56+
57+
```bash
58+
cd apps/react/text-editors/libraries/tiptap/tiptap-demo-2
59+
pnpm dev
60+
```
61+
62+
Or from the root:
63+
64+
```bash
65+
pnpm --filter @apps/react-text-editors-tiptap-tiptap-demo-2 dev
66+
```
67+
68+
### Build for Production
69+
70+
```bash
71+
pnpm --filter @apps/react-text-editors-tiptap-tiptap-demo-2 build
72+
```
73+
74+
## Structure
75+
76+
- **Framework**: react
77+
- **Document**: text-editors
78+
- **Implementation**: libraries
79+
- **Library/Solution**: tiptap
80+
- **Demo**: tiptap-demo-2
81+
82+
## Component Organization
83+
84+
- **`components/header/`** - Contains Velt components like notifications, presence indicators, header buttons
85+
- **`components/sidebar/`** - Contains sidebar-related components
86+
- **`components/document/`** - Contains the main application logic and tiptap integration
87+
- **`hooks/`** - Custom React hooks for state management and side effects
88+
- **`lib/`** - Utility functions and helpers
89+
90+
## Important Configuration
91+
92+
### .npmrc File
93+
This demo includes a `.npmrc` file that prevents pnpm from hoisting Tailwind CSS v4 from other workspace packages. This is necessary because:
94+
- This demo uses Tailwind CSS v3.4.x with traditional PostCSS configuration
95+
- Other apps in the monorepo may use Tailwind CSS v4
96+
- Without the `.npmrc`, pnpm would hoist v4 and cause PostCSS errors
97+
98+
**Do not delete the `.npmrc` file** - it ensures the correct Tailwind version is used.
99+
100+
## Next Steps
101+
102+
1. Add your tiptap implementation in `components/document/`
103+
2. Add Velt collaboration features in `components/header/`
104+
3. Update this README with specific usage instructions
105+
4. Add the demo to `master-sample-app` if it should be showcased
106+
5. Update deployment configs (Vercel, GitHub Actions) if needed
107+
108+
## Learn More
109+
110+
- [Monorepo Structure Guide](../../../../README_MONOREPO.md)
111+
- [Structure Documentation](../../../../docs/structure.md)
112+
- [Velt Documentation](https://docs.velt.dev)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// [Velt] API endpoint to generate authentication tokens
2+
import { NextRequest, NextResponse } from 'next/server';
3+
4+
export async function POST(req: NextRequest) {
5+
try {
6+
const { userId, organizationId, email, isAdmin } = await req.json();
7+
if (!userId || !organizationId) {
8+
return NextResponse.json({ error: 'Missing userId or organizationId' }, { status: 400 });
9+
}
10+
if (!process.env.VELT_AUTH_TOKEN) {
11+
return NextResponse.json({ error: 'Server configuration error: missing VELT_AUTH_TOKEN' }, { status: 500 });
12+
}
13+
const body = {
14+
data: {
15+
userId,
16+
userProperties: {
17+
organizationId,
18+
...(typeof isAdmin === 'boolean' ? { isAdmin } : {}),
19+
...(email ? { email } : {}),
20+
},
21+
},
22+
};
23+
const res = await fetch('https://api.velt.dev/v2/auth/token/get', {
24+
method: 'POST',
25+
headers: { 'Content-Type': 'application/json', 'x-velt-api-key': process.env.NEXT_PUBLIC_VELT_API_KEY!, 'x-velt-auth-token': process.env.VELT_AUTH_TOKEN! },
26+
body: JSON.stringify(body),
27+
});
28+
const json = await res.json();
29+
const token = json?.result?.data?.token;
30+
if (!res.ok || !token) {
31+
return NextResponse.json({ error: json?.error?.message || 'Failed to generate token' }, { status: 500 });
32+
}
33+
return NextResponse.json({ token });
34+
} catch {
35+
return NextResponse.json({ error: 'Internal error' }, { status: 500 });
36+
}
37+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use client';
2+
import { useMemo, useEffect, useState, useRef } from 'react';
3+
4+
export type CurrentDocument = {
5+
documentId: string;
6+
documentName: string;
7+
};
8+
9+
export function useCurrentDocument(): CurrentDocument {
10+
const [documentId, setDocumentId] = useState<string>('');
11+
const isInitialized = useRef(false);
12+
13+
useEffect(() => {
14+
if (isInitialized.current) return;
15+
16+
const urlParams = new URLSearchParams(window.location.search);
17+
let docId = urlParams.get('documentId');
18+
19+
if (docId) {
20+
setDocumentId(docId);
21+
localStorage.setItem('tiptap-document-id', docId);
22+
} else {
23+
const stored = localStorage.getItem('tiptap-document-id');
24+
if (stored) {
25+
docId = stored;
26+
} else {
27+
docId = `doc-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
28+
localStorage.setItem('tiptap-document-id', docId);
29+
}
30+
31+
const newUrl = `${window.location.pathname}?documentId=${docId}`;
32+
window.history.pushState({}, '', newUrl);
33+
34+
setDocumentId(docId);
35+
}
36+
37+
isInitialized.current = true;
38+
}, []);
39+
40+
useEffect(() => {
41+
const handleUrlChange = () => {
42+
const urlParams = new URLSearchParams(window.location.search);
43+
const docId = urlParams.get('documentId');
44+
45+
if (docId && docId !== documentId) {
46+
setDocumentId(docId);
47+
localStorage.setItem('tiptap-document-id', docId);
48+
}
49+
};
50+
51+
window.addEventListener('popstate', handleUrlChange);
52+
53+
const interval = setInterval(() => {
54+
const urlParams = new URLSearchParams(window.location.search);
55+
const docId = urlParams.get('documentId');
56+
if (docId && docId !== documentId) {
57+
handleUrlChange();
58+
}
59+
}, 1000);
60+
61+
return () => {
62+
window.removeEventListener('popstate', handleUrlChange);
63+
clearInterval(interval);
64+
};
65+
}, [documentId]);
66+
67+
return useMemo(
68+
() => ({
69+
documentId: documentId || 'loading',
70+
documentName: "TipTap Document",
71+
}),
72+
[documentId]
73+
);
74+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
'use client';
2+
3+
export { useCurrentDocument } from './DocumentContext';
4+
export type { CurrentDocument } from './DocumentContext';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { Metadata } from 'next'
2+
import '../styles/globals.css'
3+
import { AppProviders } from "@/app/userAuth/AppProviders";
4+
5+
export const metadata: Metadata = {
6+
title: 'tiptap-demo',
7+
description: 'Library demo for tiptap',
8+
}
9+
10+
export default function RootLayout({
11+
children,
12+
}: {
13+
children: React.ReactNode
14+
}) {
15+
return (
16+
<html lang="en">
17+
<body>
18+
<AppProviders>{children}</AppProviders>
19+
</body>
20+
</html>
21+
)
22+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use client";
2+
// [Velt] Provider + collaboration
3+
import { VeltProvider } from "@veltdev/react";
4+
import { useVeltAuthProvider } from "@/components/velt/VeltInitializeUser";
5+
import { VeltCollaboration } from "@/components/velt/VeltCollaboration";
6+
import DocumentCanvas from '@/components/document/document-canvas'
7+
8+
export default function Home() {
9+
// [Velt] Auth provider (reads from app/userAuth/useAppUser)
10+
const { authProvider } = useVeltAuthProvider();
11+
12+
return (
13+
// [Velt] Wrap app with VeltProvider
14+
<VeltProvider
15+
apiKey={process.env.NEXT_PUBLIC_VELT_API_KEY!}
16+
authProvider={authProvider}
17+
>
18+
{/* [Velt] Collaboration core (includes document init) */}
19+
<VeltCollaboration />
20+
21+
{/* --- App UI --- */}
22+
<DocumentCanvas />
23+
</VeltProvider>
24+
);
25+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"use client";
2+
3+
import React from "react";
4+
import { AppUserProvider } from "./AppUserContext";
5+
import { useCurrentDocument } from "@/app/document/DocumentContext";
6+
7+
/**
8+
* Client component that connects DocumentContext to AppUserProvider
9+
* This ensures users are scoped per document-id
10+
*/
11+
export function AppProviders({ children }: { children: React.ReactNode }) {
12+
const { documentId } = useCurrentDocument();
13+
14+
return (
15+
<AppUserProvider documentId={documentId}>
16+
{children}
17+
</AppUserProvider>
18+
);
19+
}

0 commit comments

Comments
 (0)