add-velt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +503 -0
- package/assets/icons/bell-icon.svg +5 -0
- package/assets/icons/inbox-icon.svg +3 -0
- package/bin/velt.js +1664 -0
- package/package.json +32 -0
package/bin/velt.js
ADDED
|
@@ -0,0 +1,1664 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
// Enhanced package manager detection (checks parent directories for workspaces)
|
|
8
|
+
function detectPackageManager(cwd) {
|
|
9
|
+
// Check current directory first
|
|
10
|
+
if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
11
|
+
if (fs.existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn';
|
|
12
|
+
if (fs.existsSync(path.join(cwd, 'package-lock.json'))) return 'npm';
|
|
13
|
+
|
|
14
|
+
// Check parent directories for workspace lockfiles
|
|
15
|
+
let dir = cwd;
|
|
16
|
+
const root = path.parse(dir).root;
|
|
17
|
+
while (dir !== root) {
|
|
18
|
+
dir = path.dirname(dir);
|
|
19
|
+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
20
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn';
|
|
21
|
+
if (fs.existsSync(path.join(dir, 'package-lock.json'))) return 'npm';
|
|
22
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
23
|
+
if (fs.existsSync(pkgPath)) {
|
|
24
|
+
try {
|
|
25
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
26
|
+
if (pkg.workspaces) break;
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return 'npm';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// DEPENDENCY DEFINITIONS (derived from sample apps)
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
const DEPS = {
|
|
38
|
+
// Baseline (always) - use v4.x.x versions
|
|
39
|
+
baseline: {
|
|
40
|
+
production: { '@veltdev/react': '^4.0.0' },
|
|
41
|
+
development: { '@veltdev/types': '^4.0.0' }
|
|
42
|
+
},
|
|
43
|
+
// Comments: no extra deps beyond baseline
|
|
44
|
+
comments: {
|
|
45
|
+
production: {},
|
|
46
|
+
development: {}
|
|
47
|
+
},
|
|
48
|
+
// Notifications: no extra deps beyond baseline
|
|
49
|
+
notifications: {
|
|
50
|
+
production: {},
|
|
51
|
+
development: {}
|
|
52
|
+
},
|
|
53
|
+
// ReactFlow CRDT - use v4.x.x versions
|
|
54
|
+
'reactflow-crdt': {
|
|
55
|
+
production: {
|
|
56
|
+
'@veltdev/reactflow-crdt': '^4.0.0',
|
|
57
|
+
'yjs': 'latest'
|
|
58
|
+
},
|
|
59
|
+
development: {}
|
|
60
|
+
},
|
|
61
|
+
// Tiptap CRDT - use v4.x.x versions
|
|
62
|
+
'tiptap-crdt': {
|
|
63
|
+
production: {
|
|
64
|
+
'@veltdev/tiptap-crdt': '^4.0.0',
|
|
65
|
+
'@veltdev/tiptap-crdt-react': '^4.0.0',
|
|
66
|
+
'@veltdev/tiptap-velt-comments': '^4.0.0',
|
|
67
|
+
'yjs': 'latest',
|
|
68
|
+
'y-prosemirror': 'latest'
|
|
69
|
+
},
|
|
70
|
+
development: {}
|
|
71
|
+
},
|
|
72
|
+
// CodeMirror CRDT - use v4.x.x versions
|
|
73
|
+
'codemirror-crdt': {
|
|
74
|
+
production: {
|
|
75
|
+
'@veltdev/codemirror-crdt': '^4.0.0',
|
|
76
|
+
'@veltdev/codemirror-crdt-react': '^4.0.0',
|
|
77
|
+
'yjs': 'latest',
|
|
78
|
+
'y-codemirror.next': 'latest'
|
|
79
|
+
},
|
|
80
|
+
development: {}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// PROJECT ANALYSIS
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
function analyzeProject(cwd) {
|
|
89
|
+
const analysis = {
|
|
90
|
+
hasNextConfig: fs.existsSync(path.join(cwd, 'next.config.js')) || fs.existsSync(path.join(cwd, 'next.config.ts')),
|
|
91
|
+
hasAppRouter: fs.existsSync(path.join(cwd, 'app')),
|
|
92
|
+
hasPagesRouter: fs.existsSync(path.join(cwd, 'pages')),
|
|
93
|
+
hasComponents: fs.existsSync(path.join(cwd, 'components')),
|
|
94
|
+
packageManager: detectPackageManager(cwd),
|
|
95
|
+
existingDeps: {}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const pkg = readPkg(cwd);
|
|
99
|
+
if (pkg) {
|
|
100
|
+
analysis.existingDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return analysis;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// FEATURE PARSING
|
|
108
|
+
// ============================================================================
|
|
109
|
+
|
|
110
|
+
function parseFeatures(args) {
|
|
111
|
+
const features = {
|
|
112
|
+
comments: false,
|
|
113
|
+
notifications: false,
|
|
114
|
+
presence: false,
|
|
115
|
+
cursors: false,
|
|
116
|
+
crdt: 'none' // 'none' | 'reactflow' | 'tiptap' | 'codemirror'
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Check for CRDT type flags
|
|
120
|
+
const crdtFlags = [];
|
|
121
|
+
if (args['reactflow-crdt']) crdtFlags.push('reactflow');
|
|
122
|
+
if (args['tiptap-crdt']) crdtFlags.push('tiptap');
|
|
123
|
+
if (args['codemirror-crdt']) crdtFlags.push('codemirror');
|
|
124
|
+
|
|
125
|
+
// Enforce only one CRDT type
|
|
126
|
+
if (crdtFlags.length > 1) {
|
|
127
|
+
console.error('Error: Only one CRDT type flag can be specified at a time.');
|
|
128
|
+
console.error('Choose one of: --reactflow-crdt, --tiptap-crdt, --codemirror-crdt');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (crdtFlags.length === 1) {
|
|
133
|
+
features.crdt = crdtFlags[0];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Handle --all flag
|
|
137
|
+
if (args.all) {
|
|
138
|
+
if (features.crdt === 'none') {
|
|
139
|
+
console.error('Error: --all requires a CRDT type flag.');
|
|
140
|
+
console.error('Usage: npx add-velt --all --reactflow-crdt');
|
|
141
|
+
console.error(' npx add-velt --all --tiptap-crdt');
|
|
142
|
+
console.error(' npx add-velt --all --codemirror-crdt');
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
features.comments = true;
|
|
146
|
+
features.notifications = true;
|
|
147
|
+
features.presence = true;
|
|
148
|
+
features.cursors = true;
|
|
149
|
+
} else {
|
|
150
|
+
features.comments = !!args.comments;
|
|
151
|
+
features.notifications = !!args.notifications;
|
|
152
|
+
features.presence = !!args.presence;
|
|
153
|
+
features.cursors = !!args.cursors;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return features;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function hasAnyFeature(features) {
|
|
160
|
+
return features.comments || features.notifications || features.presence || features.cursors || features.crdt !== 'none';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// DEPENDENCY RESOLUTION
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
function resolveDependencies(analysis, features) {
|
|
168
|
+
const deps = {
|
|
169
|
+
production: [],
|
|
170
|
+
development: []
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Helper: format package for install (use pkg@latest or just pkg for latest)
|
|
174
|
+
const formatPkg = (pkg, version) => {
|
|
175
|
+
// For 'latest', just use the package name without version to let npm resolve
|
|
176
|
+
if (version === 'latest') {
|
|
177
|
+
return pkg;
|
|
178
|
+
}
|
|
179
|
+
return `${pkg}@${version}`;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Always include baseline
|
|
183
|
+
for (const [pkg, version] of Object.entries(DEPS.baseline.production)) {
|
|
184
|
+
if (!analysis.existingDeps[pkg]) {
|
|
185
|
+
deps.production.push(formatPkg(pkg, version));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const [pkg, version] of Object.entries(DEPS.baseline.development)) {
|
|
189
|
+
if (!analysis.existingDeps[pkg]) {
|
|
190
|
+
deps.development.push(formatPkg(pkg, version));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Comments deps (none extra beyond baseline)
|
|
195
|
+
if (features.comments) {
|
|
196
|
+
for (const [pkg, version] of Object.entries(DEPS.comments.production)) {
|
|
197
|
+
if (!analysis.existingDeps[pkg]) {
|
|
198
|
+
deps.production.push(formatPkg(pkg, version));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Notifications deps (none extra beyond baseline)
|
|
204
|
+
if (features.notifications) {
|
|
205
|
+
for (const [pkg, version] of Object.entries(DEPS.notifications.production)) {
|
|
206
|
+
if (!analysis.existingDeps[pkg]) {
|
|
207
|
+
deps.production.push(formatPkg(pkg, version));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// CRDT type deps
|
|
213
|
+
if (features.crdt !== 'none') {
|
|
214
|
+
const crdtKey = `${features.crdt}-crdt`;
|
|
215
|
+
for (const [pkg, version] of Object.entries(DEPS[crdtKey].production)) {
|
|
216
|
+
if (!analysis.existingDeps[pkg]) {
|
|
217
|
+
deps.production.push(formatPkg(pkg, version));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const [pkg, version] of Object.entries(DEPS[crdtKey].development)) {
|
|
221
|
+
if (!analysis.existingDeps[pkg]) {
|
|
222
|
+
deps.development.push(formatPkg(pkg, version));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return deps;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// SMART INSTALL
|
|
232
|
+
// ============================================================================
|
|
233
|
+
|
|
234
|
+
function smartInstall(pm, deps, isDev, cwd, flags) {
|
|
235
|
+
if (!deps.length) return;
|
|
236
|
+
|
|
237
|
+
const installStrategies = [
|
|
238
|
+
{ args: [], description: 'standard install' },
|
|
239
|
+
{ args: ['--force'], description: 'with --force flag' },
|
|
240
|
+
...(pm === 'npm' ? [{ args: ['--legacy-peer-deps'], description: 'with --legacy-peer-deps' }] : []),
|
|
241
|
+
...(pm === 'npm' ? [{ args: ['--force', '--legacy-peer-deps'], description: 'with --force --legacy-peer-deps' }] : [])
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
for (const strategy of installStrategies) {
|
|
245
|
+
try {
|
|
246
|
+
console.log(`Attempting ${strategy.description}...`);
|
|
247
|
+
|
|
248
|
+
let cmd;
|
|
249
|
+
const extraArgs = [...strategy.args];
|
|
250
|
+
if (flags.force && !extraArgs.includes('--force')) extraArgs.push('--force');
|
|
251
|
+
if (flags['legacy-peer-deps'] && pm === 'npm' && !extraArgs.includes('--legacy-peer-deps')) {
|
|
252
|
+
extraArgs.push('--legacy-peer-deps');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
switch (pm) {
|
|
256
|
+
case 'pnpm':
|
|
257
|
+
cmd = `pnpm add ${isDev ? '-D' : ''} ${deps.join(' ')} ${extraArgs.join(' ')}`.trim();
|
|
258
|
+
break;
|
|
259
|
+
case 'yarn':
|
|
260
|
+
cmd = `yarn add ${isDev ? '-D' : ''} ${deps.join(' ')} ${extraArgs.filter(a => a !== '--legacy-peer-deps').join(' ')}`.trim();
|
|
261
|
+
break;
|
|
262
|
+
default:
|
|
263
|
+
cmd = `npm install ${isDev ? '--save-dev' : ''} ${deps.join(' ')} ${extraArgs.join(' ')}`.trim();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
console.log(`> ${cmd}`);
|
|
267
|
+
execSync(cmd, { stdio: 'inherit', cwd });
|
|
268
|
+
console.log(`Success with ${strategy.description}`);
|
|
269
|
+
return;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.warn(`Failed with ${strategy.description}`);
|
|
272
|
+
if (strategy === installStrategies[installStrategies.length - 1]) {
|
|
273
|
+
throw new Error(`All installation strategies failed. Last error: ${error.message}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// FILE TEMPLATES
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
// CORE: VeltInitializeDocument.tsx (same for all)
|
|
284
|
+
const TEMPLATE_VELT_INITIALIZE_DOCUMENT = `import { useEffect } from 'react';
|
|
285
|
+
import { useSetDocuments } from '@veltdev/react';
|
|
286
|
+
|
|
287
|
+
// [Velt] Initialize Velt document
|
|
288
|
+
export default function VeltInitializeDocument() {
|
|
289
|
+
// TODO: Replace with your own document ID source
|
|
290
|
+
const documentId = 'your-document-id';
|
|
291
|
+
const documentName = 'your-document-name';
|
|
292
|
+
|
|
293
|
+
const { setDocuments } = useSetDocuments();
|
|
294
|
+
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
if (!documentId) return;
|
|
297
|
+
setDocuments([
|
|
298
|
+
{ id: documentId, metadata: { documentName } },
|
|
299
|
+
]);
|
|
300
|
+
}, [setDocuments, documentId]);
|
|
301
|
+
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
`;
|
|
305
|
+
|
|
306
|
+
// CORE: VeltInitializeUser.tsx (same for all)
|
|
307
|
+
const TEMPLATE_VELT_INITIALIZE_USER = `"use client";
|
|
308
|
+
|
|
309
|
+
import type { VeltAuthProvider } from "@veltdev/types";
|
|
310
|
+
import { useMemo } from "react";
|
|
311
|
+
|
|
312
|
+
// [Velt] User authentication hook
|
|
313
|
+
// TODO: Replace with your own user retrieval logic
|
|
314
|
+
export function useVeltAuthProvider() {
|
|
315
|
+
const user = {
|
|
316
|
+
userId: 'user-id',
|
|
317
|
+
organizationId: 'organization-id',
|
|
318
|
+
email: 'user@example.com',
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const authProvider: VeltAuthProvider | undefined = useMemo(() => {
|
|
322
|
+
if (!user) return undefined;
|
|
323
|
+
return {
|
|
324
|
+
user,
|
|
325
|
+
retryConfig: { retryCount: 3, retryDelay: 1000 },
|
|
326
|
+
// TODO: Add generateToken for JWT auth
|
|
327
|
+
};
|
|
328
|
+
}, [user]);
|
|
329
|
+
|
|
330
|
+
return { authProvider };
|
|
331
|
+
}
|
|
332
|
+
`;
|
|
333
|
+
|
|
334
|
+
// CORE ONLY: VeltCollaboration.tsx (minimal)
|
|
335
|
+
const TEMPLATE_VELT_COLLABORATION_CORE = `import VeltInitializeDocument from './VeltInitializeDocument';
|
|
336
|
+
|
|
337
|
+
export function VeltCollaboration() {
|
|
338
|
+
return (
|
|
339
|
+
<>
|
|
340
|
+
<VeltInitializeDocument />
|
|
341
|
+
</>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export default VeltCollaboration;
|
|
346
|
+
`;
|
|
347
|
+
|
|
348
|
+
// COMMENTS ONLY: VeltCollaboration.tsx
|
|
349
|
+
const TEMPLATE_VELT_COLLABORATION_COMMENTS = `"use client";
|
|
350
|
+
import { VeltComments, VeltCommentsSidebar } from "@veltdev/react";
|
|
351
|
+
import VeltInitializeDocument from "./VeltInitializeDocument";
|
|
352
|
+
import { VeltCustomization } from "./ui-customization/VeltCustomization";
|
|
353
|
+
|
|
354
|
+
export function VeltCollaboration() {
|
|
355
|
+
return (
|
|
356
|
+
<>
|
|
357
|
+
<VeltInitializeDocument />
|
|
358
|
+
<VeltComments
|
|
359
|
+
popoverMode={true}
|
|
360
|
+
shadowDom={false}
|
|
361
|
+
textMode={false}
|
|
362
|
+
commentPinHighlighter={false}
|
|
363
|
+
dialogOnHover={false}
|
|
364
|
+
/>
|
|
365
|
+
<VeltCommentsSidebar groupConfig={{ enable: false }} />
|
|
366
|
+
<VeltCustomization />
|
|
367
|
+
</>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export default VeltCollaboration;
|
|
372
|
+
`;
|
|
373
|
+
|
|
374
|
+
// NOTIFICATIONS ONLY: VeltCollaboration.tsx
|
|
375
|
+
const TEMPLATE_VELT_COLLABORATION_NOTIFICATIONS = `"use client";
|
|
376
|
+
import VeltInitializeDocument from "./VeltInitializeDocument";
|
|
377
|
+
import { VeltCustomization } from "./ui-customization/VeltCustomization";
|
|
378
|
+
|
|
379
|
+
export function VeltCollaboration() {
|
|
380
|
+
return (
|
|
381
|
+
<>
|
|
382
|
+
<VeltInitializeDocument />
|
|
383
|
+
<VeltCustomization />
|
|
384
|
+
</>
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export default VeltCollaboration;
|
|
389
|
+
`;
|
|
390
|
+
|
|
391
|
+
// COMMENTS + NOTIFICATIONS: VeltCollaboration.tsx
|
|
392
|
+
const TEMPLATE_VELT_COLLABORATION_COMMENTS_NOTIFICATIONS = `"use client";
|
|
393
|
+
import { VeltComments, VeltCommentsSidebar } from "@veltdev/react";
|
|
394
|
+
import VeltInitializeDocument from "./VeltInitializeDocument";
|
|
395
|
+
import { VeltCustomization } from "./ui-customization/VeltCustomization";
|
|
396
|
+
|
|
397
|
+
export function VeltCollaboration() {
|
|
398
|
+
return (
|
|
399
|
+
<>
|
|
400
|
+
<VeltInitializeDocument />
|
|
401
|
+
<VeltComments
|
|
402
|
+
popoverMode={true}
|
|
403
|
+
shadowDom={false}
|
|
404
|
+
textMode={false}
|
|
405
|
+
commentPinHighlighter={false}
|
|
406
|
+
dialogOnHover={false}
|
|
407
|
+
/>
|
|
408
|
+
<VeltCommentsSidebar groupConfig={{ enable: false }} />
|
|
409
|
+
<VeltCustomization />
|
|
410
|
+
</>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export default VeltCollaboration;
|
|
415
|
+
`;
|
|
416
|
+
|
|
417
|
+
// REACTFLOW CRDT: VeltCollaboration.tsx
|
|
418
|
+
const TEMPLATE_VELT_COLLABORATION_REACTFLOW = `"use client";
|
|
419
|
+
import { VeltCursor, VeltComments, VeltCommentsSidebar } from "@veltdev/react";
|
|
420
|
+
import VeltInitializeDocument from "./VeltInitializeDocument";
|
|
421
|
+
import { VeltCustomization } from "./ui-customization/VeltCustomization";
|
|
422
|
+
|
|
423
|
+
export function VeltCollaboration() {
|
|
424
|
+
return (
|
|
425
|
+
<>
|
|
426
|
+
<VeltInitializeDocument />
|
|
427
|
+
<VeltComments
|
|
428
|
+
popoverMode={true}
|
|
429
|
+
textMode={false}
|
|
430
|
+
commentPinHighlighter={false}
|
|
431
|
+
dialogOnHover={false}
|
|
432
|
+
popoverTriangleComponent={false}
|
|
433
|
+
/>
|
|
434
|
+
<VeltCommentsSidebar groupConfig={{ enable: false }} />
|
|
435
|
+
<VeltCursor />
|
|
436
|
+
<VeltCustomization />
|
|
437
|
+
</>
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export default VeltCollaboration;
|
|
442
|
+
`;
|
|
443
|
+
|
|
444
|
+
// TIPTAP CRDT: VeltCollaboration.tsx (includes VeltCursor)
|
|
445
|
+
const TEMPLATE_VELT_COLLABORATION_TIPTAP = `"use client";
|
|
446
|
+
import { VeltComments, VeltCommentsSidebar, VeltCursor } from "@veltdev/react";
|
|
447
|
+
import VeltInitializeDocument from "./VeltInitializeDocument";
|
|
448
|
+
import { VeltCustomization } from "./ui-customization/VeltCustomization";
|
|
449
|
+
|
|
450
|
+
export function VeltCollaboration() {
|
|
451
|
+
return (
|
|
452
|
+
<>
|
|
453
|
+
<VeltInitializeDocument />
|
|
454
|
+
<VeltComments
|
|
455
|
+
shadowDom={false}
|
|
456
|
+
textMode={false}
|
|
457
|
+
/>
|
|
458
|
+
<VeltCommentsSidebar groupConfig={{ enable: false }} />
|
|
459
|
+
<VeltCursor />
|
|
460
|
+
<VeltCustomization />
|
|
461
|
+
</>
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export default VeltCollaboration;
|
|
466
|
+
`;
|
|
467
|
+
|
|
468
|
+
// CODEMIRROR CRDT: VeltCollaboration.tsx (includes VeltCursor)
|
|
469
|
+
const TEMPLATE_VELT_COLLABORATION_CODEMIRROR = `"use client";
|
|
470
|
+
import { VeltComments, VeltCommentsSidebar, VeltCursor } from "@veltdev/react";
|
|
471
|
+
import VeltInitializeDocument from "./VeltInitializeDocument";
|
|
472
|
+
import { VeltCustomization } from "./ui-customization/VeltCustomization";
|
|
473
|
+
|
|
474
|
+
export function VeltCollaboration() {
|
|
475
|
+
return (
|
|
476
|
+
<>
|
|
477
|
+
<VeltInitializeDocument />
|
|
478
|
+
<VeltComments textMode={false} />
|
|
479
|
+
<VeltCommentsSidebar groupConfig={{ enable: false }} />
|
|
480
|
+
<VeltCursor />
|
|
481
|
+
<VeltCustomization />
|
|
482
|
+
</>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export default VeltCollaboration;
|
|
487
|
+
`;
|
|
488
|
+
|
|
489
|
+
// ALL (comments + notifications + CRDT): VeltCollaboration variants
|
|
490
|
+
const TEMPLATE_VELT_COLLABORATION_ALL_REACTFLOW = `"use client";
|
|
491
|
+
import { VeltCursor, VeltComments, VeltCommentsSidebar } from "@veltdev/react";
|
|
492
|
+
import VeltInitializeDocument from "./VeltInitializeDocument";
|
|
493
|
+
import { VeltCustomization } from "./ui-customization/VeltCustomization";
|
|
494
|
+
|
|
495
|
+
export function VeltCollaboration() {
|
|
496
|
+
return (
|
|
497
|
+
<>
|
|
498
|
+
<VeltInitializeDocument />
|
|
499
|
+
<VeltComments
|
|
500
|
+
popoverMode={true}
|
|
501
|
+
shadowDom={false}
|
|
502
|
+
textMode={false}
|
|
503
|
+
commentPinHighlighter={false}
|
|
504
|
+
dialogOnHover={false}
|
|
505
|
+
popoverTriangleComponent={false}
|
|
506
|
+
/>
|
|
507
|
+
<VeltCommentsSidebar groupConfig={{ enable: false }} />
|
|
508
|
+
<VeltCursor />
|
|
509
|
+
<VeltCustomization />
|
|
510
|
+
</>
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export default VeltCollaboration;
|
|
515
|
+
`;
|
|
516
|
+
|
|
517
|
+
const TEMPLATE_VELT_COLLABORATION_ALL_TIPTAP = `"use client";
|
|
518
|
+
import { VeltComments, VeltCommentsSidebar, VeltCursor } from "@veltdev/react";
|
|
519
|
+
import VeltInitializeDocument from "./VeltInitializeDocument";
|
|
520
|
+
import { VeltCustomization } from "./ui-customization/VeltCustomization";
|
|
521
|
+
|
|
522
|
+
export function VeltCollaboration() {
|
|
523
|
+
return (
|
|
524
|
+
<>
|
|
525
|
+
<VeltInitializeDocument />
|
|
526
|
+
<VeltComments
|
|
527
|
+
shadowDom={false}
|
|
528
|
+
textMode={false}
|
|
529
|
+
/>
|
|
530
|
+
<VeltCommentsSidebar groupConfig={{ enable: false }} />
|
|
531
|
+
<VeltCursor />
|
|
532
|
+
<VeltCustomization />
|
|
533
|
+
</>
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export default VeltCollaboration;
|
|
538
|
+
`;
|
|
539
|
+
|
|
540
|
+
const TEMPLATE_VELT_COLLABORATION_ALL_CODEMIRROR = `"use client";
|
|
541
|
+
import { VeltComments, VeltCommentsSidebar, VeltCursor } from "@veltdev/react";
|
|
542
|
+
import VeltInitializeDocument from "./VeltInitializeDocument";
|
|
543
|
+
import { VeltCustomization } from "./ui-customization/VeltCustomization";
|
|
544
|
+
|
|
545
|
+
export function VeltCollaboration() {
|
|
546
|
+
return (
|
|
547
|
+
<>
|
|
548
|
+
<VeltInitializeDocument />
|
|
549
|
+
<VeltComments textMode={false} />
|
|
550
|
+
<VeltCommentsSidebar groupConfig={{ enable: false }} />
|
|
551
|
+
<VeltCursor />
|
|
552
|
+
<VeltCustomization />
|
|
553
|
+
</>
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export default VeltCollaboration;
|
|
558
|
+
`;
|
|
559
|
+
|
|
560
|
+
// VeltTools: COMMENTS ONLY (no notifications)
|
|
561
|
+
const TEMPLATE_VELT_TOOLS_COMMENTS = `"use client";
|
|
562
|
+
import { VeltPresence, VeltSidebarButton } from "@veltdev/react";
|
|
563
|
+
|
|
564
|
+
function VeltTools() {
|
|
565
|
+
return (
|
|
566
|
+
<>
|
|
567
|
+
<VeltPresence />
|
|
568
|
+
<VeltSidebarButton />
|
|
569
|
+
</>
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export default VeltTools;
|
|
574
|
+
`;
|
|
575
|
+
|
|
576
|
+
// VeltTools: NOTIFICATIONS ONLY (no sidebar)
|
|
577
|
+
const TEMPLATE_VELT_TOOLS_NOTIFICATIONS = `"use client";
|
|
578
|
+
import { VeltPresence, VeltNotificationsTool } from "@veltdev/react";
|
|
579
|
+
|
|
580
|
+
function VeltTools() {
|
|
581
|
+
return (
|
|
582
|
+
<>
|
|
583
|
+
<VeltPresence />
|
|
584
|
+
<VeltNotificationsTool
|
|
585
|
+
settings={true}
|
|
586
|
+
shadowDom={false}
|
|
587
|
+
tabConfig={{
|
|
588
|
+
forYou: { name: "For You", enable: true },
|
|
589
|
+
documents: { name: "Documents", enable: true },
|
|
590
|
+
all: { name: "All", enable: true },
|
|
591
|
+
}}
|
|
592
|
+
/>
|
|
593
|
+
</>
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export default VeltTools;
|
|
598
|
+
`;
|
|
599
|
+
|
|
600
|
+
// VeltTools: COMMENTS + NOTIFICATIONS
|
|
601
|
+
const TEMPLATE_VELT_TOOLS_COMMENTS_NOTIFICATIONS = `"use client";
|
|
602
|
+
import { VeltPresence, VeltSidebarButton, VeltNotificationsTool } from "@veltdev/react";
|
|
603
|
+
|
|
604
|
+
function VeltTools() {
|
|
605
|
+
return (
|
|
606
|
+
<>
|
|
607
|
+
<VeltPresence />
|
|
608
|
+
<VeltSidebarButton />
|
|
609
|
+
<VeltNotificationsTool
|
|
610
|
+
settings={true}
|
|
611
|
+
shadowDom={false}
|
|
612
|
+
tabConfig={{
|
|
613
|
+
forYou: { name: "For You", enable: true },
|
|
614
|
+
documents: { name: "Documents", enable: true },
|
|
615
|
+
all: { name: "All", enable: true },
|
|
616
|
+
}}
|
|
617
|
+
/>
|
|
618
|
+
</>
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export default VeltTools;
|
|
623
|
+
`;
|
|
624
|
+
|
|
625
|
+
// VeltTools: CRDT with huddle (for reactflow)
|
|
626
|
+
const TEMPLATE_VELT_TOOLS_CRDT_REACTFLOW = `"use client";
|
|
627
|
+
import { VeltPresence, VeltSidebarButton, VeltNotificationsTool, VeltHuddleTool } from "@veltdev/react";
|
|
628
|
+
|
|
629
|
+
function VeltTools() {
|
|
630
|
+
return (
|
|
631
|
+
<>
|
|
632
|
+
<VeltPresence />
|
|
633
|
+
<VeltSidebarButton />
|
|
634
|
+
<VeltNotificationsTool
|
|
635
|
+
settings={true}
|
|
636
|
+
shadowDom={false}
|
|
637
|
+
tabConfig={{
|
|
638
|
+
forYou: { name: "For You", enable: true },
|
|
639
|
+
documents: { name: "Documents", enable: true },
|
|
640
|
+
all: { name: "All", enable: true },
|
|
641
|
+
}}
|
|
642
|
+
/>
|
|
643
|
+
<VeltHuddleTool type='all' />
|
|
644
|
+
</>
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export default VeltTools;
|
|
649
|
+
`;
|
|
650
|
+
|
|
651
|
+
// VeltTools: CRDT without huddle (for text editors)
|
|
652
|
+
const TEMPLATE_VELT_TOOLS_CRDT_TEXT = `"use client";
|
|
653
|
+
import { VeltPresence, VeltSidebarButton, VeltNotificationsTool } from "@veltdev/react";
|
|
654
|
+
|
|
655
|
+
function VeltTools() {
|
|
656
|
+
return (
|
|
657
|
+
<>
|
|
658
|
+
<VeltPresence />
|
|
659
|
+
<VeltSidebarButton />
|
|
660
|
+
<VeltNotificationsTool
|
|
661
|
+
settings={true}
|
|
662
|
+
shadowDom={false}
|
|
663
|
+
tabConfig={{
|
|
664
|
+
forYou: { name: "For You", enable: true },
|
|
665
|
+
documents: { name: "Documents", enable: true },
|
|
666
|
+
all: { name: "All", enable: true },
|
|
667
|
+
}}
|
|
668
|
+
/>
|
|
669
|
+
</>
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export default VeltTools;
|
|
674
|
+
`;
|
|
675
|
+
|
|
676
|
+
// UI CUSTOMIZATION: Comments only
|
|
677
|
+
const TEMPLATE_VELT_CUSTOMIZATION_COMMENTS = `"use client";
|
|
678
|
+
import { useVeltClient, VeltWireframe } from "@veltdev/react";
|
|
679
|
+
import VeltCommentBubbleWf from "./VeltCommentBubbleWf";
|
|
680
|
+
import VeltCommentToolWf from "./VeltCommentToolWf";
|
|
681
|
+
import "./styles.css";
|
|
682
|
+
import { useEffect } from "react";
|
|
683
|
+
|
|
684
|
+
export function VeltCustomization() {
|
|
685
|
+
const { client } = useVeltClient();
|
|
686
|
+
|
|
687
|
+
useEffect(() => {
|
|
688
|
+
if (client) {
|
|
689
|
+
client.setDarkMode(true);
|
|
690
|
+
}
|
|
691
|
+
}, [client]);
|
|
692
|
+
|
|
693
|
+
return (
|
|
694
|
+
<VeltWireframe>
|
|
695
|
+
<VeltCommentBubbleWf />
|
|
696
|
+
<VeltCommentToolWf />
|
|
697
|
+
</VeltWireframe>
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
`;
|
|
701
|
+
|
|
702
|
+
// UI CUSTOMIZATION: Notifications only
|
|
703
|
+
const TEMPLATE_VELT_CUSTOMIZATION_NOTIFICATIONS = `"use client";
|
|
704
|
+
import { useVeltClient, VeltWireframe } from "@veltdev/react";
|
|
705
|
+
import VeltNotificationsToolWf from "./VeltNotificationsToolWf";
|
|
706
|
+
import "./styles.css";
|
|
707
|
+
import { useEffect } from "react";
|
|
708
|
+
|
|
709
|
+
export function VeltCustomization() {
|
|
710
|
+
const { client } = useVeltClient();
|
|
711
|
+
|
|
712
|
+
useEffect(() => {
|
|
713
|
+
if (client) {
|
|
714
|
+
client.setDarkMode(true);
|
|
715
|
+
}
|
|
716
|
+
}, [client]);
|
|
717
|
+
|
|
718
|
+
return (
|
|
719
|
+
<VeltWireframe>
|
|
720
|
+
<VeltNotificationsToolWf />
|
|
721
|
+
</VeltWireframe>
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
`;
|
|
725
|
+
|
|
726
|
+
// UI CUSTOMIZATION: Comments + Notifications
|
|
727
|
+
const TEMPLATE_VELT_CUSTOMIZATION_COMMENTS_NOTIFICATIONS = `"use client";
|
|
728
|
+
import { useVeltClient, VeltWireframe } from "@veltdev/react";
|
|
729
|
+
import VeltCommentBubbleWf from "./VeltCommentBubbleWf";
|
|
730
|
+
import VeltCommentToolWf from "./VeltCommentToolWf";
|
|
731
|
+
import VeltNotificationsToolWf from "./VeltNotificationsToolWf";
|
|
732
|
+
import VeltSidebarButtonWf from "./VeltSidebarButtonWf";
|
|
733
|
+
import "./styles.css";
|
|
734
|
+
import { useEffect } from "react";
|
|
735
|
+
|
|
736
|
+
export function VeltCustomization() {
|
|
737
|
+
const { client } = useVeltClient();
|
|
738
|
+
|
|
739
|
+
useEffect(() => {
|
|
740
|
+
if (client) {
|
|
741
|
+
client.setDarkMode(true);
|
|
742
|
+
}
|
|
743
|
+
}, [client]);
|
|
744
|
+
|
|
745
|
+
return (
|
|
746
|
+
<VeltWireframe>
|
|
747
|
+
<VeltCommentBubbleWf />
|
|
748
|
+
<VeltCommentToolWf />
|
|
749
|
+
<VeltNotificationsToolWf />
|
|
750
|
+
<VeltSidebarButtonWf />
|
|
751
|
+
</VeltWireframe>
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
`;
|
|
755
|
+
|
|
756
|
+
// UI CUSTOMIZATION: CRDT (all wireframes)
|
|
757
|
+
const TEMPLATE_VELT_CUSTOMIZATION_CRDT = `"use client";
|
|
758
|
+
import { useVeltClient, VeltWireframe } from "@veltdev/react";
|
|
759
|
+
import VeltCommentBubbleWf from "./VeltCommentBubbleWf";
|
|
760
|
+
import VeltCommentToolWf from "./VeltCommentToolWf";
|
|
761
|
+
import VeltNotificationsToolWf from "./VeltNotificationsToolWf";
|
|
762
|
+
import VeltSidebarButtonWf from "./VeltSidebarButtonWf";
|
|
763
|
+
import "./styles.css";
|
|
764
|
+
import { useEffect } from "react";
|
|
765
|
+
|
|
766
|
+
export function VeltCustomization() {
|
|
767
|
+
const { client } = useVeltClient();
|
|
768
|
+
|
|
769
|
+
useEffect(() => {
|
|
770
|
+
if (client) {
|
|
771
|
+
client.setDarkMode(true);
|
|
772
|
+
}
|
|
773
|
+
}, [client]);
|
|
774
|
+
|
|
775
|
+
return (
|
|
776
|
+
<VeltWireframe>
|
|
777
|
+
<VeltCommentBubbleWf />
|
|
778
|
+
<VeltCommentToolWf />
|
|
779
|
+
<VeltNotificationsToolWf />
|
|
780
|
+
<VeltSidebarButtonWf />
|
|
781
|
+
</VeltWireframe>
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
`;
|
|
785
|
+
|
|
786
|
+
// Wireframe components
|
|
787
|
+
const TEMPLATE_VELT_COMMENT_BUBBLE_WF = `"use client";
|
|
788
|
+
import { VeltCommentBubbleWireframe, VeltIf } from '@veltdev/react';
|
|
789
|
+
|
|
790
|
+
const VeltCommentBubbleWf = () => {
|
|
791
|
+
return (
|
|
792
|
+
<VeltCommentBubbleWireframe>
|
|
793
|
+
<div style={{ position: 'relative', cursor: 'pointer', display: 'flex', alignItems: 'center' }}>
|
|
794
|
+
<VeltIf condition="{commentAnnotation.comments.length} > 0">
|
|
795
|
+
<div style={{
|
|
796
|
+
background: 'rgba(255, 255, 255, 0.08)',
|
|
797
|
+
borderRadius: '18.667px',
|
|
798
|
+
padding: '4.667px 8px 4.667px 6.222px',
|
|
799
|
+
display: 'flex',
|
|
800
|
+
alignItems: 'center',
|
|
801
|
+
justifyContent: 'center',
|
|
802
|
+
gap: '3.111px',
|
|
803
|
+
height: '28px',
|
|
804
|
+
flexShrink: 0,
|
|
805
|
+
boxSizing: 'border-box'
|
|
806
|
+
}}>
|
|
807
|
+
<div style={{
|
|
808
|
+
width: '15.556px',
|
|
809
|
+
height: '15.556px',
|
|
810
|
+
position: 'relative',
|
|
811
|
+
overflow: 'clip',
|
|
812
|
+
flexShrink: 0
|
|
813
|
+
}}>
|
|
814
|
+
<div style={{
|
|
815
|
+
position: 'absolute',
|
|
816
|
+
left: 'calc(50% + 0.492px)',
|
|
817
|
+
top: '50%',
|
|
818
|
+
transform: 'translate(-50%, -50%)',
|
|
819
|
+
width: '11px',
|
|
820
|
+
height: '11px',
|
|
821
|
+
border: '1.5px solid white',
|
|
822
|
+
borderRadius: '6px 6px 6px 1px',
|
|
823
|
+
boxSizing: 'border-box'
|
|
824
|
+
}} />
|
|
825
|
+
</div>
|
|
826
|
+
<p style={{
|
|
827
|
+
fontFamily: 'Urbanist, sans-serif',
|
|
828
|
+
fontWeight: 700,
|
|
829
|
+
fontSize: '14px',
|
|
830
|
+
lineHeight: '1.3',
|
|
831
|
+
color: 'white',
|
|
832
|
+
margin: 0,
|
|
833
|
+
whiteSpace: 'pre',
|
|
834
|
+
flexShrink: 0
|
|
835
|
+
}}>
|
|
836
|
+
<VeltCommentBubbleWireframe.CommentsCount />
|
|
837
|
+
</p>
|
|
838
|
+
</div>
|
|
839
|
+
</VeltIf>
|
|
840
|
+
<VeltIf condition="{commentAnnotation.comments.length} === 0">
|
|
841
|
+
<div style={{ width: '0px', height: '0px', flexShrink: 0 }} />
|
|
842
|
+
</VeltIf>
|
|
843
|
+
</div>
|
|
844
|
+
</VeltCommentBubbleWireframe>
|
|
845
|
+
);
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
export default VeltCommentBubbleWf;
|
|
849
|
+
`;
|
|
850
|
+
|
|
851
|
+
const TEMPLATE_VELT_COMMENT_TOOL_WF = `"use client";
|
|
852
|
+
import { VeltCommentToolWireframe } from '@veltdev/react';
|
|
853
|
+
|
|
854
|
+
const VeltCommentToolWf = () => {
|
|
855
|
+
return (
|
|
856
|
+
<VeltCommentToolWireframe>
|
|
857
|
+
<div style={{
|
|
858
|
+
background: '#141414',
|
|
859
|
+
border: 'none',
|
|
860
|
+
padding: '4.667px 6.32px 4.667px 6.222px',
|
|
861
|
+
cursor: 'pointer',
|
|
862
|
+
display: 'flex',
|
|
863
|
+
alignItems: 'center',
|
|
864
|
+
justifyContent: 'center',
|
|
865
|
+
width: '28px',
|
|
866
|
+
height: '28px',
|
|
867
|
+
borderRadius: '18.667px',
|
|
868
|
+
boxSizing: 'border-box'
|
|
869
|
+
}}>
|
|
870
|
+
<div style={{
|
|
871
|
+
width: '15.556px',
|
|
872
|
+
height: '15.556px',
|
|
873
|
+
position: 'relative',
|
|
874
|
+
overflow: 'clip',
|
|
875
|
+
flexShrink: 0
|
|
876
|
+
}}>
|
|
877
|
+
<div style={{
|
|
878
|
+
position: 'absolute',
|
|
879
|
+
left: 'calc(50% + 0.492px)',
|
|
880
|
+
top: '50%',
|
|
881
|
+
transform: 'translate(-50%, -50%)',
|
|
882
|
+
width: '11px',
|
|
883
|
+
height: '11px',
|
|
884
|
+
border: '0.972px solid white',
|
|
885
|
+
borderRadius: '6px 6px 6px 1px',
|
|
886
|
+
boxSizing: 'border-box'
|
|
887
|
+
}} />
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
890
|
+
</VeltCommentToolWireframe>
|
|
891
|
+
);
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
export default VeltCommentToolWf;
|
|
895
|
+
`;
|
|
896
|
+
|
|
897
|
+
const TEMPLATE_VELT_NOTIFICATIONS_TOOL_WF = `"use client";
|
|
898
|
+
import { VeltNotificationsToolWireframe } from '@veltdev/react';
|
|
899
|
+
|
|
900
|
+
const bellIcon = "/icons/bell-icon.svg";
|
|
901
|
+
|
|
902
|
+
const VeltNotificationsToolWf = () => {
|
|
903
|
+
return (
|
|
904
|
+
<VeltNotificationsToolWireframe>
|
|
905
|
+
<div style={{
|
|
906
|
+
background: '#141414',
|
|
907
|
+
borderRadius: '23.333px',
|
|
908
|
+
padding: '5.833px 4px 5.833px 7px',
|
|
909
|
+
display: 'flex',
|
|
910
|
+
alignItems: 'center',
|
|
911
|
+
justifyContent: 'center',
|
|
912
|
+
gap: '3.889px',
|
|
913
|
+
cursor: 'pointer',
|
|
914
|
+
boxSizing: 'border-box',
|
|
915
|
+
flexShrink: 0
|
|
916
|
+
}}>
|
|
917
|
+
<div style={{ width: '19.444px', height: '19.444px', position: 'relative', flexShrink: 0 }}>
|
|
918
|
+
<img src={bellIcon} alt="" style={{ width: '100%', height: '100%', display: 'block' }} />
|
|
919
|
+
</div>
|
|
920
|
+
<div style={{
|
|
921
|
+
fontFamily: 'Poppins, sans-serif',
|
|
922
|
+
fontWeight: 500,
|
|
923
|
+
fontSize: '14px',
|
|
924
|
+
lineHeight: '1.5',
|
|
925
|
+
color: 'white',
|
|
926
|
+
whiteSpace: 'pre',
|
|
927
|
+
flexShrink: 0
|
|
928
|
+
}}>
|
|
929
|
+
<VeltNotificationsToolWireframe.UnreadCount />
|
|
930
|
+
</div>
|
|
931
|
+
</div>
|
|
932
|
+
</VeltNotificationsToolWireframe>
|
|
933
|
+
);
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
export default VeltNotificationsToolWf;
|
|
937
|
+
`;
|
|
938
|
+
|
|
939
|
+
const TEMPLATE_VELT_SIDEBAR_BUTTON_WF = `"use client";
|
|
940
|
+
import { VeltSidebarButtonWireframe } from '@veltdev/react';
|
|
941
|
+
|
|
942
|
+
const inboxIcon = "/icons/inbox-icon.svg";
|
|
943
|
+
|
|
944
|
+
const VeltSidebarButtonWf = () => {
|
|
945
|
+
return (
|
|
946
|
+
<VeltSidebarButtonWireframe>
|
|
947
|
+
<div style={{
|
|
948
|
+
background: '#141414',
|
|
949
|
+
borderRadius: '24px',
|
|
950
|
+
padding: '6px 7px 6px 12px',
|
|
951
|
+
display: 'flex',
|
|
952
|
+
alignItems: 'center',
|
|
953
|
+
justifyContent: 'center',
|
|
954
|
+
gap: '4px',
|
|
955
|
+
height: '28px',
|
|
956
|
+
cursor: 'pointer',
|
|
957
|
+
boxSizing: 'border-box',
|
|
958
|
+
flexShrink: 0
|
|
959
|
+
}}>
|
|
960
|
+
<div style={{ width: '19.444px', height: '19.444px', position: 'relative', flexShrink: 0 }}>
|
|
961
|
+
<img src={inboxIcon} alt="" style={{ width: '100%', height: '100%', display: 'block' }} />
|
|
962
|
+
</div>
|
|
963
|
+
<div style={{
|
|
964
|
+
fontFamily: 'Poppins, sans-serif',
|
|
965
|
+
fontWeight: 500,
|
|
966
|
+
fontSize: '14px',
|
|
967
|
+
lineHeight: '1.5',
|
|
968
|
+
color: 'white',
|
|
969
|
+
whiteSpace: 'pre',
|
|
970
|
+
flexShrink: 0
|
|
971
|
+
}}>
|
|
972
|
+
<VeltSidebarButtonWireframe.CommentsCount />
|
|
973
|
+
</div>
|
|
974
|
+
</div>
|
|
975
|
+
</VeltSidebarButtonWireframe>
|
|
976
|
+
);
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
export default VeltSidebarButtonWf;
|
|
980
|
+
`;
|
|
981
|
+
|
|
982
|
+
const TEMPLATE_STYLES_CSS = `/* [Velt] UI customization styles */
|
|
983
|
+
|
|
984
|
+
velt-comment-dialog::part(triangle) {
|
|
985
|
+
border-color: rgb(255, 205, 46) !important;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
velt-comment-dialog::part(triangle-fill) {
|
|
989
|
+
fill: rgb(255, 205, 46) !important;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/* Dark Mode Theme */
|
|
993
|
+
--velt-dark-mode-accent: #141414;
|
|
994
|
+
--velt-dark-mode-accent-text: #ffffff;
|
|
995
|
+
--velt-dark-mode-background-0: #000000;
|
|
996
|
+
--velt-dark-mode-background-2: #141414;
|
|
997
|
+
--velt-dark-mode-text-0: #ffffff;
|
|
998
|
+
`;
|
|
999
|
+
|
|
1000
|
+
// ============================================================================
|
|
1001
|
+
// DYNAMIC TEMPLATE GENERATORS
|
|
1002
|
+
// ============================================================================
|
|
1003
|
+
|
|
1004
|
+
function generateVeltCollaborationTemplate(features) {
|
|
1005
|
+
const hasComments = features.comments;
|
|
1006
|
+
const hasCursors = features.cursors;
|
|
1007
|
+
const hasCrdt = features.crdt !== 'none';
|
|
1008
|
+
const crdtType = features.crdt;
|
|
1009
|
+
|
|
1010
|
+
// Build imports
|
|
1011
|
+
const veltImports = [];
|
|
1012
|
+
if (hasComments) veltImports.push('VeltComments', 'VeltCommentsSidebar');
|
|
1013
|
+
if (hasCursors) veltImports.push('VeltCursor');
|
|
1014
|
+
|
|
1015
|
+
const hasVeltImports = veltImports.length > 0;
|
|
1016
|
+
const hasCustomization = hasComments || features.notifications;
|
|
1017
|
+
|
|
1018
|
+
// Determine comment props based on CRDT type
|
|
1019
|
+
let commentProps = '';
|
|
1020
|
+
if (hasComments) {
|
|
1021
|
+
if (crdtType === 'reactflow') {
|
|
1022
|
+
commentProps = `
|
|
1023
|
+
popoverMode={true}
|
|
1024
|
+
textMode={false}
|
|
1025
|
+
commentPinHighlighter={false}
|
|
1026
|
+
dialogOnHover={false}
|
|
1027
|
+
popoverTriangleComponent={false}`;
|
|
1028
|
+
} else if (crdtType === 'tiptap') {
|
|
1029
|
+
commentProps = `
|
|
1030
|
+
shadowDom={false}
|
|
1031
|
+
textMode={false}`;
|
|
1032
|
+
} else if (crdtType === 'codemirror') {
|
|
1033
|
+
commentProps = ` textMode={false}`;
|
|
1034
|
+
} else {
|
|
1035
|
+
commentProps = `
|
|
1036
|
+
popoverMode={true}
|
|
1037
|
+
shadowDom={false}
|
|
1038
|
+
textMode={false}
|
|
1039
|
+
commentPinHighlighter={false}
|
|
1040
|
+
dialogOnHover={false}`;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Build template
|
|
1045
|
+
let template = `"use client";
|
|
1046
|
+
`;
|
|
1047
|
+
|
|
1048
|
+
if (hasVeltImports) {
|
|
1049
|
+
template += `import { ${veltImports.join(', ')} } from "@veltdev/react";
|
|
1050
|
+
`;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
template += `import VeltInitializeDocument from "./VeltInitializeDocument";
|
|
1054
|
+
`;
|
|
1055
|
+
|
|
1056
|
+
if (hasCustomization) {
|
|
1057
|
+
template += `import { VeltCustomization } from "./ui-customization/VeltCustomization";
|
|
1058
|
+
`;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
template += `
|
|
1062
|
+
export function VeltCollaboration() {
|
|
1063
|
+
return (
|
|
1064
|
+
<>
|
|
1065
|
+
<VeltInitializeDocument />
|
|
1066
|
+
`;
|
|
1067
|
+
|
|
1068
|
+
if (hasComments) {
|
|
1069
|
+
template += ` <VeltComments${commentProps}
|
|
1070
|
+
/>
|
|
1071
|
+
<VeltCommentsSidebar groupConfig={{ enable: false }} />
|
|
1072
|
+
`;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if (hasCursors) {
|
|
1076
|
+
template += ` <VeltCursor />
|
|
1077
|
+
`;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
if (hasCustomization) {
|
|
1081
|
+
template += ` <VeltCustomization />
|
|
1082
|
+
`;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
template += ` </>
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
export default VeltCollaboration;
|
|
1090
|
+
`;
|
|
1091
|
+
|
|
1092
|
+
return template;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function generateVeltToolsTemplate(features) {
|
|
1096
|
+
const hasPresence = features.presence;
|
|
1097
|
+
const hasComments = features.comments;
|
|
1098
|
+
const hasNotifications = features.notifications;
|
|
1099
|
+
const hasCrdt = features.crdt !== 'none';
|
|
1100
|
+
const crdtType = features.crdt;
|
|
1101
|
+
|
|
1102
|
+
// Build imports
|
|
1103
|
+
const veltImports = [];
|
|
1104
|
+
if (hasPresence) veltImports.push('VeltPresence');
|
|
1105
|
+
if (hasComments) veltImports.push('VeltSidebarButton');
|
|
1106
|
+
if (hasNotifications) veltImports.push('VeltNotificationsTool');
|
|
1107
|
+
if (hasCrdt && crdtType === 'reactflow') veltImports.push('VeltHuddleTool');
|
|
1108
|
+
|
|
1109
|
+
if (veltImports.length === 0) {
|
|
1110
|
+
return null; // No VeltTools needed
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
let template = `"use client";
|
|
1114
|
+
import { ${veltImports.join(', ')} } from "@veltdev/react";
|
|
1115
|
+
|
|
1116
|
+
function VeltTools() {
|
|
1117
|
+
return (
|
|
1118
|
+
<>
|
|
1119
|
+
`;
|
|
1120
|
+
|
|
1121
|
+
if (hasPresence) {
|
|
1122
|
+
template += ` <VeltPresence />
|
|
1123
|
+
`;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
if (hasComments) {
|
|
1127
|
+
template += ` <VeltSidebarButton />
|
|
1128
|
+
`;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
if (hasNotifications) {
|
|
1132
|
+
template += ` <VeltNotificationsTool
|
|
1133
|
+
settings={true}
|
|
1134
|
+
shadowDom={false}
|
|
1135
|
+
tabConfig={{
|
|
1136
|
+
forYou: { name: "For You", enable: true },
|
|
1137
|
+
documents: { name: "Documents", enable: true },
|
|
1138
|
+
all: { name: "All", enable: true },
|
|
1139
|
+
}}
|
|
1140
|
+
/>
|
|
1141
|
+
`;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
if (hasCrdt && crdtType === 'reactflow') {
|
|
1145
|
+
template += ` <VeltHuddleTool type='all' />
|
|
1146
|
+
`;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
template += ` </>
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
export default VeltTools;
|
|
1154
|
+
`;
|
|
1155
|
+
|
|
1156
|
+
return template;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// ============================================================================
|
|
1160
|
+
// FILE GENERATION LOGIC
|
|
1161
|
+
// ============================================================================
|
|
1162
|
+
|
|
1163
|
+
function generateVeltConfig(features) {
|
|
1164
|
+
const files = {};
|
|
1165
|
+
|
|
1166
|
+
// Always include core files
|
|
1167
|
+
files['components/velt/VeltInitializeDocument.tsx'] = TEMPLATE_VELT_INITIALIZE_DOCUMENT;
|
|
1168
|
+
files['components/velt/VeltInitializeUser.tsx'] = TEMPLATE_VELT_INITIALIZE_USER;
|
|
1169
|
+
|
|
1170
|
+
const hasComments = features.comments;
|
|
1171
|
+
const hasNotifications = features.notifications;
|
|
1172
|
+
const hasPresence = features.presence;
|
|
1173
|
+
const hasCursors = features.cursors;
|
|
1174
|
+
const hasCrdt = features.crdt !== 'none';
|
|
1175
|
+
|
|
1176
|
+
// Generate VeltCollaboration dynamically based on features
|
|
1177
|
+
files['components/velt/VeltCollaboration.tsx'] = generateVeltCollaborationTemplate(features);
|
|
1178
|
+
|
|
1179
|
+
// Generate VeltTools dynamically based on features
|
|
1180
|
+
const veltToolsTemplate = generateVeltToolsTemplate(features);
|
|
1181
|
+
if (veltToolsTemplate) {
|
|
1182
|
+
files['components/velt/VeltTools.tsx'] = veltToolsTemplate;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// Add UI customization files based on features
|
|
1186
|
+
if (hasCrdt || (hasComments && hasNotifications)) {
|
|
1187
|
+
// Full customization
|
|
1188
|
+
files['components/velt/ui-customization/styles.css'] = TEMPLATE_STYLES_CSS;
|
|
1189
|
+
files['components/velt/ui-customization/VeltCustomization.tsx'] = TEMPLATE_VELT_CUSTOMIZATION_CRDT;
|
|
1190
|
+
files['components/velt/ui-customization/VeltCommentBubbleWf.tsx'] = TEMPLATE_VELT_COMMENT_BUBBLE_WF;
|
|
1191
|
+
files['components/velt/ui-customization/VeltCommentToolWf.tsx'] = TEMPLATE_VELT_COMMENT_TOOL_WF;
|
|
1192
|
+
files['components/velt/ui-customization/VeltNotificationsToolWf.tsx'] = TEMPLATE_VELT_NOTIFICATIONS_TOOL_WF;
|
|
1193
|
+
files['components/velt/ui-customization/VeltSidebarButtonWf.tsx'] = TEMPLATE_VELT_SIDEBAR_BUTTON_WF;
|
|
1194
|
+
} else if (hasComments) {
|
|
1195
|
+
// Comments only
|
|
1196
|
+
files['components/velt/ui-customization/styles.css'] = TEMPLATE_STYLES_CSS;
|
|
1197
|
+
files['components/velt/ui-customization/VeltCustomization.tsx'] = TEMPLATE_VELT_CUSTOMIZATION_COMMENTS;
|
|
1198
|
+
files['components/velt/ui-customization/VeltCommentBubbleWf.tsx'] = TEMPLATE_VELT_COMMENT_BUBBLE_WF;
|
|
1199
|
+
files['components/velt/ui-customization/VeltCommentToolWf.tsx'] = TEMPLATE_VELT_COMMENT_TOOL_WF;
|
|
1200
|
+
} else if (hasNotifications) {
|
|
1201
|
+
// Notifications only
|
|
1202
|
+
files['components/velt/ui-customization/styles.css'] = TEMPLATE_STYLES_CSS;
|
|
1203
|
+
files['components/velt/ui-customization/VeltCustomization.tsx'] = TEMPLATE_VELT_CUSTOMIZATION_NOTIFICATIONS;
|
|
1204
|
+
files['components/velt/ui-customization/VeltNotificationsToolWf.tsx'] = TEMPLATE_VELT_NOTIFICATIONS_TOOL_WF;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
return files;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// ============================================================================
|
|
1211
|
+
// MAIN FUNCTIONS
|
|
1212
|
+
// ============================================================================
|
|
1213
|
+
|
|
1214
|
+
function main() {
|
|
1215
|
+
const args = parseArgs(process.argv);
|
|
1216
|
+
|
|
1217
|
+
if (args.help || args.h) {
|
|
1218
|
+
printUsage();
|
|
1219
|
+
process.exit(0);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
const cwd = process.cwd();
|
|
1223
|
+
console.log('Analyzing existing project...');
|
|
1224
|
+
const analysis = analyzeProject(cwd);
|
|
1225
|
+
const features = parseFeatures(args);
|
|
1226
|
+
|
|
1227
|
+
console.log(`Project Analysis:
|
|
1228
|
+
- Package Manager: ${analysis.packageManager}
|
|
1229
|
+
- Router Type: ${analysis.hasAppRouter ? 'App Router' : analysis.hasPagesRouter ? 'Pages Router' : 'Unknown'}
|
|
1230
|
+
- Has Components: ${analysis.hasComponents ? 'Yes' : 'No'}
|
|
1231
|
+
`);
|
|
1232
|
+
|
|
1233
|
+
// Show enabled features
|
|
1234
|
+
const enabledFeatures = [];
|
|
1235
|
+
if (features.presence) enabledFeatures.push('presence');
|
|
1236
|
+
if (features.cursors) enabledFeatures.push('cursors');
|
|
1237
|
+
if (features.comments) enabledFeatures.push('comments');
|
|
1238
|
+
if (features.notifications) enabledFeatures.push('notifications');
|
|
1239
|
+
if (features.crdt !== 'none') enabledFeatures.push(`${features.crdt}-crdt`);
|
|
1240
|
+
|
|
1241
|
+
if (enabledFeatures.length > 0) {
|
|
1242
|
+
console.log(`Features enabled: ${enabledFeatures.join(', ')}\n`);
|
|
1243
|
+
} else {
|
|
1244
|
+
console.log('Features enabled: core only\n');
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const flags = {
|
|
1248
|
+
force: !!args.force || !!args.f,
|
|
1249
|
+
'legacy-peer-deps': !!args['legacy-peer-deps'],
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
try {
|
|
1253
|
+
console.log('Generating Velt integration files...');
|
|
1254
|
+
const filesWritten = applyScaffold({ cwd, flags, features });
|
|
1255
|
+
|
|
1256
|
+
console.log('\nInstalling dependencies...');
|
|
1257
|
+
const deps = resolveDependencies(analysis, features);
|
|
1258
|
+
|
|
1259
|
+
if (deps.production.length) {
|
|
1260
|
+
smartInstall(analysis.packageManager, deps.production, false, cwd, flags);
|
|
1261
|
+
}
|
|
1262
|
+
if (deps.development.length) {
|
|
1263
|
+
smartInstall(analysis.packageManager, deps.development, true, cwd, flags);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// Copy icon assets if features use them
|
|
1267
|
+
if (hasAnyFeature(features)) {
|
|
1268
|
+
console.log('\nCopying icon assets...');
|
|
1269
|
+
copyIconAssets(cwd);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
generateSetupInstructions(analysis, features, filesWritten, deps);
|
|
1273
|
+
console.log(`\nVelt integration complete!`);
|
|
1274
|
+
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
console.error(`Installation failed: ${error.message}`);
|
|
1277
|
+
console.log('\nTroubleshooting tips:');
|
|
1278
|
+
console.log(' - Try: npx add-velt --force');
|
|
1279
|
+
console.log(' - Or: npx add-velt --legacy-peer-deps');
|
|
1280
|
+
process.exit(1);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
function applyScaffold({ cwd, flags, features }) {
|
|
1285
|
+
const files = generateVeltConfig(features);
|
|
1286
|
+
const filesWritten = [];
|
|
1287
|
+
|
|
1288
|
+
Object.entries(files).forEach(([relPath, content]) => {
|
|
1289
|
+
const outPath = path.join(cwd, relPath);
|
|
1290
|
+
const dir = path.dirname(outPath);
|
|
1291
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1292
|
+
if (fs.existsSync(outPath) && !flags.force) {
|
|
1293
|
+
console.log(`Skipping existing file: ${relPath}`);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
fs.writeFileSync(outPath, content, 'utf8');
|
|
1298
|
+
console.log(`Wrote ${relPath}`);
|
|
1299
|
+
filesWritten.push(relPath);
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
return filesWritten;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// ============================================================================
|
|
1306
|
+
// AUTO-WIRING: VeltProvider + VeltCollaboration
|
|
1307
|
+
// ============================================================================
|
|
1308
|
+
|
|
1309
|
+
function autoWireVeltProvider(cwd, analysis) {
|
|
1310
|
+
// Detect layout file
|
|
1311
|
+
const appLayoutPaths = [
|
|
1312
|
+
path.join(cwd, 'app', 'layout.tsx'),
|
|
1313
|
+
path.join(cwd, 'app', 'layout.jsx'),
|
|
1314
|
+
path.join(cwd, 'src', 'app', 'layout.tsx'),
|
|
1315
|
+
path.join(cwd, 'src', 'app', 'layout.jsx')
|
|
1316
|
+
];
|
|
1317
|
+
|
|
1318
|
+
const pagesAppPaths = [
|
|
1319
|
+
path.join(cwd, 'pages', '_app.tsx'),
|
|
1320
|
+
path.join(cwd, 'pages', '_app.jsx'),
|
|
1321
|
+
path.join(cwd, 'src', 'pages', '_app.tsx'),
|
|
1322
|
+
path.join(cwd, 'src', 'pages', '_app.jsx')
|
|
1323
|
+
];
|
|
1324
|
+
|
|
1325
|
+
let layoutPath = null;
|
|
1326
|
+
let isAppRouter = false;
|
|
1327
|
+
|
|
1328
|
+
// Prefer App Router
|
|
1329
|
+
for (const p of appLayoutPaths) {
|
|
1330
|
+
if (fs.existsSync(p)) {
|
|
1331
|
+
layoutPath = p;
|
|
1332
|
+
isAppRouter = true;
|
|
1333
|
+
break;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// Fall back to Pages Router
|
|
1338
|
+
if (!layoutPath) {
|
|
1339
|
+
for (const p of pagesAppPaths) {
|
|
1340
|
+
if (fs.existsSync(p)) {
|
|
1341
|
+
layoutPath = p;
|
|
1342
|
+
isAppRouter = false;
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (!layoutPath) {
|
|
1349
|
+
console.log('\nNo layout file found. Skipping auto-wiring.');
|
|
1350
|
+
console.log('You will need to manually wrap your app with VeltProvider.');
|
|
1351
|
+
return { success: false, reason: 'no-layout' };
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
let content = fs.readFileSync(layoutPath, 'utf8');
|
|
1355
|
+
const relPath = path.relative(cwd, layoutPath);
|
|
1356
|
+
|
|
1357
|
+
// Check if already wired
|
|
1358
|
+
if (content.includes('VeltProvider') && content.includes('VeltCollaboration')) {
|
|
1359
|
+
console.log(`\nVelt already wired in ${relPath}. Skipping.`);
|
|
1360
|
+
return { success: true, already: true, file: relPath };
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// Determine import path for components
|
|
1364
|
+
const isInSrc = layoutPath.includes(path.join('src', 'app')) || layoutPath.includes(path.join('src', 'pages'));
|
|
1365
|
+
const componentImportBase = isInSrc ? '@/components' : '@/components';
|
|
1366
|
+
|
|
1367
|
+
// Build the required imports
|
|
1368
|
+
const veltImports = [];
|
|
1369
|
+
|
|
1370
|
+
if (!content.includes("from '@veltdev/react'") && !content.includes('from "@veltdev/react"')) {
|
|
1371
|
+
veltImports.push(`import { VeltProvider } from '@veltdev/react';`);
|
|
1372
|
+
} else if (!content.includes('VeltProvider')) {
|
|
1373
|
+
// VeltProvider needs to be added to existing import
|
|
1374
|
+
content = content.replace(
|
|
1375
|
+
/(import\s*\{[^}]*)(}\s*from\s*['"]@veltdev\/react['"])/,
|
|
1376
|
+
'$1, VeltProvider$2'
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
if (!content.includes('useVeltAuthProvider')) {
|
|
1381
|
+
veltImports.push(`import { useVeltAuthProvider } from '${componentImportBase}/velt/VeltInitializeUser';`);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
if (!content.includes('VeltCollaboration')) {
|
|
1385
|
+
veltImports.push(`import { VeltCollaboration } from '${componentImportBase}/velt/VeltCollaboration';`);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// Insert imports at the top (after existing imports)
|
|
1389
|
+
if (veltImports.length > 0) {
|
|
1390
|
+
// Find the last import statement
|
|
1391
|
+
const importMatches = [...content.matchAll(/^import\s+.*?['"][^'"]+['"];?\s*$/gm)];
|
|
1392
|
+
if (importMatches.length > 0) {
|
|
1393
|
+
const lastImport = importMatches[importMatches.length - 1];
|
|
1394
|
+
const insertPos = lastImport.index + lastImport[0].length;
|
|
1395
|
+
content = content.slice(0, insertPos) + '\n' + veltImports.join('\n') + content.slice(insertPos);
|
|
1396
|
+
} else {
|
|
1397
|
+
// No imports found, add at the very top
|
|
1398
|
+
content = veltImports.join('\n') + '\n\n' + content;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// Add VeltClientWrapper component if not present
|
|
1403
|
+
if (!content.includes('VeltClientWrapper')) {
|
|
1404
|
+
const wrapperComponent = `
|
|
1405
|
+
// Velt wrapper component for client-side provider
|
|
1406
|
+
"use client";
|
|
1407
|
+
function VeltClientWrapper({ children }: { children: React.ReactNode }) {
|
|
1408
|
+
const { authProvider } = useVeltAuthProvider();
|
|
1409
|
+
return (
|
|
1410
|
+
<VeltProvider apiKey={process.env.NEXT_PUBLIC_VELT_API_KEY || 'YOUR_API_KEY'} authProvider={authProvider}>
|
|
1411
|
+
<VeltCollaboration />
|
|
1412
|
+
{children}
|
|
1413
|
+
</VeltProvider>
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
`;
|
|
1417
|
+
// Insert before the default export
|
|
1418
|
+
const exportMatch = content.match(/export\s+default\s+function\s+\w+/);
|
|
1419
|
+
if (exportMatch) {
|
|
1420
|
+
content = content.slice(0, exportMatch.index) + wrapperComponent + '\n' + content.slice(exportMatch.index);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// Wrap the body children with VeltClientWrapper
|
|
1425
|
+
if (isAppRouter) {
|
|
1426
|
+
// For App Router, wrap body children
|
|
1427
|
+
// Pattern: <body ...>{children}</body> -> <body ...><VeltClientWrapper>{children}</VeltClientWrapper></body>
|
|
1428
|
+
if (!content.includes('<VeltClientWrapper>') && !content.includes('<VeltProvider')) {
|
|
1429
|
+
// Try to wrap {children} inside body
|
|
1430
|
+
content = content.replace(
|
|
1431
|
+
/(<body[^>]*>)([\s\S]*?)(\{children\})([\s\S]*?)(<\/body>)/,
|
|
1432
|
+
'$1$2<VeltClientWrapper>$3</VeltClientWrapper>$4$5'
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
} else {
|
|
1436
|
+
// For Pages Router, wrap Component
|
|
1437
|
+
if (!content.includes('<VeltClientWrapper>') && !content.includes('<VeltProvider')) {
|
|
1438
|
+
content = content.replace(
|
|
1439
|
+
/(<Component\s+\{\.\.\.pageProps\}\s*\/>)/,
|
|
1440
|
+
'<VeltClientWrapper><Component {...pageProps} /></VeltClientWrapper>'
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// Write the modified content
|
|
1446
|
+
fs.writeFileSync(layoutPath, content, 'utf8');
|
|
1447
|
+
console.log(`\nAuto-wired VeltProvider + VeltCollaboration in ${relPath}`);
|
|
1448
|
+
|
|
1449
|
+
return { success: true, file: relPath };
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// Copy icon assets from CLI to user's public/icons
|
|
1453
|
+
function copyIconAssets(cwd) {
|
|
1454
|
+
try {
|
|
1455
|
+
const publicIconsDir = path.join(cwd, 'public', 'icons');
|
|
1456
|
+
if (!fs.existsSync(publicIconsDir)) {
|
|
1457
|
+
fs.mkdirSync(publicIconsDir, { recursive: true });
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// Get the CLI's assets directory
|
|
1461
|
+
const cliAssetsDir = path.join(__dirname, '..', 'assets', 'icons');
|
|
1462
|
+
|
|
1463
|
+
const iconFiles = ['bell-icon.svg', 'inbox-icon.svg'];
|
|
1464
|
+
iconFiles.forEach(iconFile => {
|
|
1465
|
+
const srcPath = path.join(cliAssetsDir, iconFile);
|
|
1466
|
+
const destPath = path.join(publicIconsDir, iconFile);
|
|
1467
|
+
|
|
1468
|
+
if (fs.existsSync(srcPath) && !fs.existsSync(destPath)) {
|
|
1469
|
+
fs.copyFileSync(srcPath, destPath);
|
|
1470
|
+
console.log(`Copied public/icons/${iconFile}`);
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
} catch (error) {
|
|
1474
|
+
console.warn(`Warning: Failed to copy icon files: ${error.message}`);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
function generateSetupInstructions(analysis, features, filesWritten, deps) {
|
|
1479
|
+
console.log('\n----------------------------------------');
|
|
1480
|
+
console.log('FILES CREATED:');
|
|
1481
|
+
console.log('----------------------------------------');
|
|
1482
|
+
|
|
1483
|
+
const coreFiles = filesWritten.filter(f => !f.includes('ui-customization'));
|
|
1484
|
+
const uiFiles = filesWritten.filter(f => f.includes('ui-customization'));
|
|
1485
|
+
|
|
1486
|
+
console.log('\nCore components:');
|
|
1487
|
+
coreFiles.forEach(f => console.log(` - ${f}`));
|
|
1488
|
+
|
|
1489
|
+
if (uiFiles.length > 0) {
|
|
1490
|
+
console.log('\nUI customization:');
|
|
1491
|
+
uiFiles.forEach(f => console.log(` - ${f}`));
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
console.log('\n----------------------------------------');
|
|
1495
|
+
console.log('DEPENDENCIES INSTALLED:');
|
|
1496
|
+
console.log('----------------------------------------');
|
|
1497
|
+
if (deps.production.length) {
|
|
1498
|
+
console.log('\nProduction:');
|
|
1499
|
+
deps.production.forEach(d => console.log(` - ${d}`));
|
|
1500
|
+
}
|
|
1501
|
+
if (deps.development.length) {
|
|
1502
|
+
console.log('\nDevelopment:');
|
|
1503
|
+
deps.development.forEach(d => console.log(` - ${d}`));
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
console.log('\n----------------------------------------');
|
|
1507
|
+
console.log('NEXT STEPS:');
|
|
1508
|
+
console.log('----------------------------------------');
|
|
1509
|
+
|
|
1510
|
+
let stepNum = 1;
|
|
1511
|
+
|
|
1512
|
+
console.log(`\n${stepNum++}. Get your API key from https://console.velt.dev`);
|
|
1513
|
+
console.log(`\n${stepNum++}. Wrap your app with VeltProvider in your layout file`);
|
|
1514
|
+
console.log(` Set NEXT_PUBLIC_VELT_API_KEY in your .env.local file`);
|
|
1515
|
+
|
|
1516
|
+
console.log(`\n${stepNum++}. Update VeltInitializeDocument.tsx with your document ID source`);
|
|
1517
|
+
console.log(`\n${stepNum++}. Update VeltInitializeUser.tsx with your user authentication logic`);
|
|
1518
|
+
|
|
1519
|
+
if (hasAnyFeature(features)) {
|
|
1520
|
+
console.log(`\n${stepNum++}. Add <VeltTools /> to your toolbar area for Presence, Sidebar, and Notifications`);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
if (features.crdt !== 'none') {
|
|
1524
|
+
console.log(`\n${stepNum++}. For ${features.crdt.toUpperCase()} CRDT integration, see:`);
|
|
1525
|
+
if (features.crdt === 'reactflow') {
|
|
1526
|
+
console.log(' https://docs.velt.dev/async-collaboration/comments/setup/react-flow');
|
|
1527
|
+
} else if (features.crdt === 'tiptap') {
|
|
1528
|
+
console.log(' https://docs.velt.dev/realtime-collaboration/single-editor-mode/tiptap');
|
|
1529
|
+
} else {
|
|
1530
|
+
console.log(' https://docs.velt.dev/realtime-collaboration/single-editor-mode/codemirror');
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// Features summary
|
|
1535
|
+
console.log('\n----------------------------------------');
|
|
1536
|
+
console.log('FEATURES ENABLED:');
|
|
1537
|
+
console.log('----------------------------------------');
|
|
1538
|
+
if (features.presence) {
|
|
1539
|
+
console.log('\n - Presence: VeltPresence (shows online users)');
|
|
1540
|
+
}
|
|
1541
|
+
if (features.cursors) {
|
|
1542
|
+
console.log(' - Cursors: VeltCursor (shows live cursor positions)');
|
|
1543
|
+
}
|
|
1544
|
+
if (features.comments) {
|
|
1545
|
+
console.log(' - Comments: VeltComments + VeltCommentsSidebar');
|
|
1546
|
+
}
|
|
1547
|
+
if (features.notifications) {
|
|
1548
|
+
console.log(' - Notifications: VeltNotificationsTool');
|
|
1549
|
+
}
|
|
1550
|
+
if (features.crdt !== 'none') {
|
|
1551
|
+
console.log(` - CRDT: ${features.crdt} real-time sync`);
|
|
1552
|
+
}
|
|
1553
|
+
if (!hasAnyFeature(features)) {
|
|
1554
|
+
console.log('\n - Core only (VeltProvider + VeltInitialize)');
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
function parseArgs(argv) {
|
|
1559
|
+
const args = argv.slice(2);
|
|
1560
|
+
const result = { _: [] };
|
|
1561
|
+
for (let i = 0; i < args.length; i++) {
|
|
1562
|
+
const token = args[i];
|
|
1563
|
+
if (token.startsWith('--')) {
|
|
1564
|
+
if (token.includes('=')) {
|
|
1565
|
+
const [fullKey, ...valueParts] = token.split('=');
|
|
1566
|
+
const key = fullKey.replace(/^--/, '');
|
|
1567
|
+
let value = valueParts.join('=');
|
|
1568
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
1569
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
1570
|
+
value = value.slice(1, -1);
|
|
1571
|
+
}
|
|
1572
|
+
result[key] = value;
|
|
1573
|
+
} else {
|
|
1574
|
+
const key = token.replace(/^--/, '');
|
|
1575
|
+
const next = args[i + 1];
|
|
1576
|
+
if (next && !next.startsWith('-')) {
|
|
1577
|
+
let value = next;
|
|
1578
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
1579
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
1580
|
+
value = value.slice(1, -1);
|
|
1581
|
+
}
|
|
1582
|
+
result[key] = value;
|
|
1583
|
+
i++;
|
|
1584
|
+
} else {
|
|
1585
|
+
result[key] = true;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
} else if (token.startsWith('-')) {
|
|
1589
|
+
const key = token.replace(/^-+/, '');
|
|
1590
|
+
result[key] = true;
|
|
1591
|
+
} else {
|
|
1592
|
+
result._.push(token);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return result;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
function readPkg(cwd) {
|
|
1599
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
1600
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
1601
|
+
return JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
function printUsage() {
|
|
1605
|
+
console.log(`
|
|
1606
|
+
add-velt - Add Velt collaboration to Next.js apps
|
|
1607
|
+
|
|
1608
|
+
Usage:
|
|
1609
|
+
npx add-velt [options]
|
|
1610
|
+
|
|
1611
|
+
Feature Flags:
|
|
1612
|
+
--presence Add presence (VeltPresence - shows online users)
|
|
1613
|
+
--cursors Add cursors (VeltCursor - shows live cursor positions)
|
|
1614
|
+
--comments Add comments (VeltComments, VeltCommentsSidebar)
|
|
1615
|
+
--notifications Add notifications (VeltNotificationsTool)
|
|
1616
|
+
|
|
1617
|
+
CRDT Type Flags (choose one):
|
|
1618
|
+
--reactflow-crdt Add ReactFlow CRDT (canvas collaboration)
|
|
1619
|
+
--tiptap-crdt Add Tiptap CRDT (rich text editor)
|
|
1620
|
+
--codemirror-crdt Add CodeMirror CRDT (code editor)
|
|
1621
|
+
|
|
1622
|
+
Combined:
|
|
1623
|
+
--all Enable presence + cursors + comments + notifications + CRDT
|
|
1624
|
+
(requires a CRDT type flag)
|
|
1625
|
+
|
|
1626
|
+
Installation Flags:
|
|
1627
|
+
--force, -f Force overwrite existing files
|
|
1628
|
+
--legacy-peer-deps Use legacy peer deps (npm only)
|
|
1629
|
+
--help, -h Show this help message
|
|
1630
|
+
|
|
1631
|
+
Examples:
|
|
1632
|
+
npx add-velt # Core only
|
|
1633
|
+
npx add-velt --presence # Presence only
|
|
1634
|
+
npx add-velt --cursors # Cursors only
|
|
1635
|
+
npx add-velt --comments # Comments only
|
|
1636
|
+
npx add-velt --notifications # Notifications only
|
|
1637
|
+
npx add-velt --presence --cursors # Presence + Cursors
|
|
1638
|
+
npx add-velt --comments --notifications # Comments + Notifications
|
|
1639
|
+
npx add-velt --reactflow-crdt # ReactFlow CRDT only
|
|
1640
|
+
npx add-velt --tiptap-crdt # Tiptap CRDT only
|
|
1641
|
+
npx add-velt --codemirror-crdt # CodeMirror CRDT only
|
|
1642
|
+
npx add-velt --all --reactflow-crdt # All features with ReactFlow
|
|
1643
|
+
npx add-velt --all --tiptap-crdt # All features with Tiptap
|
|
1644
|
+
npx add-velt --all --codemirror-crdt # All features with CodeMirror
|
|
1645
|
+
|
|
1646
|
+
Note:
|
|
1647
|
+
- Only ONE CRDT type can be selected at a time
|
|
1648
|
+
- --all requires a CRDT type flag
|
|
1649
|
+
- Get your API key from https://console.velt.dev
|
|
1650
|
+
`);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
if (require.main === module) {
|
|
1654
|
+
main();
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
module.exports = {
|
|
1658
|
+
analyzeProject,
|
|
1659
|
+
resolveDependencies,
|
|
1660
|
+
detectPackageManager,
|
|
1661
|
+
smartInstall,
|
|
1662
|
+
parseFeatures,
|
|
1663
|
+
generateVeltConfig
|
|
1664
|
+
};
|