@rgby/collab-core 1.0.1
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 +60 -0
- package/dist/collab-provider.d.ts +561 -0
- package/dist/collab-provider.d.ts.map +1 -0
- package/dist/collab-provider.js +1389 -0
- package/dist/collab-provider.js.map +1 -0
- package/dist/extensions/collaboration-cursor.d.ts +87 -0
- package/dist/extensions/collaboration-cursor.d.ts.map +1 -0
- package/dist/extensions/collaboration-cursor.js +82 -0
- package/dist/extensions/collaboration-cursor.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/services/sw-manager.d.ts +62 -0
- package/dist/services/sw-manager.d.ts.map +1 -0
- package/dist/services/sw-manager.js +207 -0
- package/dist/services/sw-manager.js.map +1 -0
- package/dist/test-deps.d.ts +15 -0
- package/dist/test-deps.d.ts.map +1 -0
- package/dist/test-deps.js +21 -0
- package/dist/test-deps.js.map +1 -0
- package/dist/upload-queue.d.ts +82 -0
- package/dist/upload-queue.d.ts.map +1 -0
- package/dist/upload-queue.js +273 -0
- package/dist/upload-queue.js.map +1 -0
- package/dist/useCollab.d.ts +216 -0
- package/dist/useCollab.d.ts.map +1 -0
- package/dist/useCollab.js +583 -0
- package/dist/useCollab.js.map +1 -0
- package/package.json +93 -0
- package/src/collab-provider.ts +1871 -0
- package/src/extensions/collaboration-cursor.ts +188 -0
- package/src/index.ts +6 -0
- package/src/services/sw-manager.ts +255 -0
- package/src/test-deps.ts +34 -0
- package/src/upload-queue.ts +337 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core'
|
|
2
|
+
import type { DecorationAttrs } from '@tiptap/pm/view'
|
|
3
|
+
import { defaultSelectionBuilder, yCursorPlugin } from '@tiptap/y-tiptap'
|
|
4
|
+
|
|
5
|
+
type CollaborationCaretStorage = {
|
|
6
|
+
users: { clientId: number;[key: string]: any }[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CollaborationCaretOptions {
|
|
10
|
+
/**
|
|
11
|
+
* The Hocuspocus provider instance. This can also be a TiptapCloudProvider instance.
|
|
12
|
+
* @type {HocuspocusProvider | TiptapCloudProvider}
|
|
13
|
+
* @example new HocuspocusProvider()
|
|
14
|
+
*/
|
|
15
|
+
provider: any
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The user details object – feel free to add properties to this object as needed
|
|
19
|
+
* @example { name: 'John Doe', color: '#305500' }
|
|
20
|
+
*/
|
|
21
|
+
user: Record<string, any>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A function that returns a DOM element for the cursor.
|
|
25
|
+
* @param user The user details object
|
|
26
|
+
* @example
|
|
27
|
+
* render: user => {
|
|
28
|
+
* const cursor = document.createElement('span')
|
|
29
|
+
* cursor.classList.add('collaboration-carets__caret')
|
|
30
|
+
* cursor.setAttribute('style', `border-color: ${user.color}`)
|
|
31
|
+
*
|
|
32
|
+
* const label = document.createElement('div')
|
|
33
|
+
* label.classList.add('collaboration-carets__label')
|
|
34
|
+
* label.setAttribute('style', `background-color: ${user.color}`)
|
|
35
|
+
* label.insertBefore(document.createTextNode(user.name), null)
|
|
36
|
+
*
|
|
37
|
+
* cursor.insertBefore(label, null)
|
|
38
|
+
* return cursor
|
|
39
|
+
* }
|
|
40
|
+
*/
|
|
41
|
+
render(user: Record<string, any>): HTMLElement
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A function that returns a ProseMirror DecorationAttrs object for the selection.
|
|
45
|
+
* @param user The user details object
|
|
46
|
+
* @example
|
|
47
|
+
* selectionRender: user => {
|
|
48
|
+
* return {
|
|
49
|
+
* nodeName: 'span',
|
|
50
|
+
* class: 'collaboration-carets__selection',
|
|
51
|
+
* style: `background-color: ${user.color}`,
|
|
52
|
+
* 'data-user': user.name,
|
|
53
|
+
* }
|
|
54
|
+
*/
|
|
55
|
+
selectionRender(user: Record<string, any>): DecorationAttrs
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @deprecated The "onUpdate" option is deprecated. Please use `editor.storage.collaborationCaret.users` instead. Read more: https://tiptap.dev/api/extensions/collaboration-caret
|
|
59
|
+
*/
|
|
60
|
+
onUpdate: (users: { clientId: number;[key: string]: any }[]) => null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
declare module '@tiptap/core' {
|
|
64
|
+
interface Commands<ReturnType> {
|
|
65
|
+
collaborationCaret: {
|
|
66
|
+
/**
|
|
67
|
+
* Update details of the current user
|
|
68
|
+
* @example editor.commands.updateUser({ name: 'John Doe', color: '#305500' })
|
|
69
|
+
*/
|
|
70
|
+
updateUser: (attributes: Record<string, any>) => ReturnType
|
|
71
|
+
/**
|
|
72
|
+
* Update details of the current user
|
|
73
|
+
*
|
|
74
|
+
* @deprecated The "user" command is deprecated. Please use "updateUser" instead. Read more: https://tiptap.dev/api/extensions/collaboration-caret
|
|
75
|
+
*/
|
|
76
|
+
user: (attributes: Record<string, any>) => ReturnType
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface Storage {
|
|
81
|
+
collaborationCaret: CollaborationCaretStorage
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const awarenessStatesToArray = (states: Map<number, Record<string, any>>) => {
|
|
86
|
+
return Array.from(states.entries()).map(([key, value]) => {
|
|
87
|
+
return {
|
|
88
|
+
clientId: key,
|
|
89
|
+
...value.user,
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const defaultOnUpdate = () => null
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* This extension allows you to add collaboration carets to your editor.
|
|
98
|
+
* @see https://tiptap.dev/api/extensions/collaboration-caret
|
|
99
|
+
*/
|
|
100
|
+
export const CollaborationCursor = Extension.create<CollaborationCaretOptions, CollaborationCaretStorage>({
|
|
101
|
+
name: 'collaborationCursor', // Kept generic name to match expectation
|
|
102
|
+
|
|
103
|
+
priority: 999,
|
|
104
|
+
|
|
105
|
+
addOptions() {
|
|
106
|
+
return {
|
|
107
|
+
provider: null,
|
|
108
|
+
user: {
|
|
109
|
+
name: null,
|
|
110
|
+
color: null,
|
|
111
|
+
},
|
|
112
|
+
render: user => {
|
|
113
|
+
const cursor = document.createElement('span')
|
|
114
|
+
|
|
115
|
+
cursor.classList.add('collaboration-cursor__caret')
|
|
116
|
+
cursor.setAttribute('style', `border-color: ${user.color}`)
|
|
117
|
+
|
|
118
|
+
const label = document.createElement('div')
|
|
119
|
+
|
|
120
|
+
label.classList.add('collaboration-cursor__label')
|
|
121
|
+
label.setAttribute('style', `background-color: ${user.color}`)
|
|
122
|
+
label.insertBefore(document.createTextNode(user.name), null)
|
|
123
|
+
cursor.insertBefore(label, null)
|
|
124
|
+
|
|
125
|
+
return cursor
|
|
126
|
+
},
|
|
127
|
+
selectionRender: defaultSelectionBuilder,
|
|
128
|
+
onUpdate: defaultOnUpdate,
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
onCreate() {
|
|
133
|
+
if (this.options.onUpdate !== defaultOnUpdate) {
|
|
134
|
+
console.warn(
|
|
135
|
+
'[tiptap warn]: DEPRECATED: The "onUpdate" option is deprecated. Please use `editor.storage.collaborationCaret.users` instead. Read more: https://tiptap.dev/api/extensions/collaboration-caret',
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
if (!this.options.provider) {
|
|
139
|
+
throw new Error('The "provider" option is required for the CollaborationCaret extension')
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
addStorage() {
|
|
144
|
+
return {
|
|
145
|
+
users: [],
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
addCommands() {
|
|
150
|
+
return {
|
|
151
|
+
updateUser: (attributes: Record<string, any>) => () => {
|
|
152
|
+
this.options.provider.awareness.setLocalStateField('user', attributes)
|
|
153
|
+
return true
|
|
154
|
+
},
|
|
155
|
+
user:
|
|
156
|
+
(attributes: Record<string, any>) =>
|
|
157
|
+
({ editor }) => {
|
|
158
|
+
console.warn(
|
|
159
|
+
'[tiptap warn]: DEPRECATED: The "user" command is deprecated. Please use "updateUser" instead. Read more: https://tiptap.dev/api/extensions/collaboration-caret',
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return editor.commands.updateUser(attributes)
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
addProseMirrorPlugins() {
|
|
168
|
+
return [
|
|
169
|
+
yCursorPlugin(
|
|
170
|
+
(() => {
|
|
171
|
+
this.options.provider.awareness.setLocalStateField('user', this.options.user)
|
|
172
|
+
|
|
173
|
+
this.storage.users = awarenessStatesToArray(this.options.provider.awareness.states)
|
|
174
|
+
|
|
175
|
+
this.options.provider.awareness.on('update', () => {
|
|
176
|
+
this.storage.users = awarenessStatesToArray(this.options.provider.awareness.states)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
return this.options.provider.awareness
|
|
180
|
+
})(),
|
|
181
|
+
{
|
|
182
|
+
cursorBuilder: this.options.render,
|
|
183
|
+
selectionBuilder: this.options.selectionRender,
|
|
184
|
+
},
|
|
185
|
+
),
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Worker Manager
|
|
3
|
+
* Handles SW registration, updates, and cache management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ServiceWorkerManagerOptions {
|
|
7
|
+
scriptUrl?: string;
|
|
8
|
+
scope?: string;
|
|
9
|
+
onUpdate?: (registration: ServiceWorkerRegistration) => void;
|
|
10
|
+
onReady?: (registration: ServiceWorkerRegistration) => void;
|
|
11
|
+
onError?: (error: Error) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ServiceWorkerManager {
|
|
15
|
+
private registration: ServiceWorkerRegistration | null = null;
|
|
16
|
+
private options: ServiceWorkerManagerOptions;
|
|
17
|
+
private updateCheckInterval: ReturnType<typeof setInterval> | null = null;
|
|
18
|
+
|
|
19
|
+
constructor(options: ServiceWorkerManagerOptions = {}) {
|
|
20
|
+
this.options = {
|
|
21
|
+
scriptUrl: options.scriptUrl || '/sw.js',
|
|
22
|
+
scope: options.scope || '/',
|
|
23
|
+
...options
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Register the Service Worker
|
|
29
|
+
*/
|
|
30
|
+
async register(): Promise<ServiceWorkerRegistration | null> {
|
|
31
|
+
// Check if Service Worker is supported
|
|
32
|
+
if (!('serviceWorker' in navigator)) {
|
|
33
|
+
console.warn('[SW Manager] Service Workers not supported in this browser');
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if we're in a secure context (HTTPS or localhost)
|
|
38
|
+
if (!window.isSecureContext) {
|
|
39
|
+
console.warn('[SW Manager] Service Workers require a secure context (HTTPS)');
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
console.log('[SW Manager] Registering Service Worker...');
|
|
45
|
+
const registration = await navigator.serviceWorker.register(
|
|
46
|
+
this.options.scriptUrl!,
|
|
47
|
+
{ scope: this.options.scope }
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
this.registration = registration;
|
|
51
|
+
|
|
52
|
+
// Handle updates
|
|
53
|
+
registration.addEventListener('updatefound', () => {
|
|
54
|
+
const newWorker = registration.installing;
|
|
55
|
+
if (!newWorker) return;
|
|
56
|
+
|
|
57
|
+
newWorker.addEventListener('statechange', () => {
|
|
58
|
+
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
|
59
|
+
console.log('[SW Manager] New version available');
|
|
60
|
+
if (this.options.onUpdate) {
|
|
61
|
+
this.options.onUpdate(registration);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Check if SW is already active
|
|
68
|
+
if (registration.active) {
|
|
69
|
+
console.log('[SW Manager] Service Worker active');
|
|
70
|
+
if (this.options.onReady) {
|
|
71
|
+
this.options.onReady(registration);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Periodically check for updates (every 1 hour)
|
|
76
|
+
this.updateCheckInterval = setInterval(() => {
|
|
77
|
+
registration.update().catch((error) => {
|
|
78
|
+
console.error('[SW Manager] Update check failed:', error);
|
|
79
|
+
});
|
|
80
|
+
}, 60 * 60 * 1000);
|
|
81
|
+
|
|
82
|
+
console.log('[SW Manager] Service Worker registered successfully');
|
|
83
|
+
return registration;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('[SW Manager] Registration failed:', error);
|
|
86
|
+
if (this.options.onError) {
|
|
87
|
+
this.options.onError(error as Error);
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Unregister the Service Worker
|
|
95
|
+
*/
|
|
96
|
+
async unregister(): Promise<boolean> {
|
|
97
|
+
if (!this.registration) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const success = await this.registration.unregister();
|
|
103
|
+
if (success) {
|
|
104
|
+
console.log('[SW Manager] Service Worker unregistered');
|
|
105
|
+
this.registration = null;
|
|
106
|
+
if (this.updateCheckInterval) {
|
|
107
|
+
clearInterval(this.updateCheckInterval);
|
|
108
|
+
this.updateCheckInterval = null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return success;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('[SW Manager] Unregistration failed:', error);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Update the Service Worker
|
|
120
|
+
*/
|
|
121
|
+
async update(): Promise<void> {
|
|
122
|
+
if (!this.registration) {
|
|
123
|
+
throw new Error('No Service Worker registered');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await this.registration.update();
|
|
128
|
+
console.log('[SW Manager] Update check complete');
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('[SW Manager] Update failed:', error);
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Skip waiting and activate new Service Worker immediately
|
|
137
|
+
*/
|
|
138
|
+
async skipWaiting(): Promise<void> {
|
|
139
|
+
if (!this.registration || !this.registration.waiting) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Send message to waiting SW to skip waiting
|
|
144
|
+
this.registration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
|
145
|
+
|
|
146
|
+
// Wait for the new SW to take control
|
|
147
|
+
return new Promise<void>((resolve) => {
|
|
148
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
149
|
+
console.log('[SW Manager] New Service Worker activated');
|
|
150
|
+
resolve();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Clear all caches
|
|
157
|
+
*/
|
|
158
|
+
async clearCache(): Promise<boolean> {
|
|
159
|
+
if (!this.registration || !this.registration.active) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return new Promise((resolve) => {
|
|
164
|
+
const messageChannel = new MessageChannel();
|
|
165
|
+
messageChannel.port1.onmessage = (event) => {
|
|
166
|
+
resolve(event.data.success || false);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
this.registration!.active!.postMessage(
|
|
170
|
+
{ type: 'CLEAR_CACHE' },
|
|
171
|
+
[messageChannel.port2]
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get current cache size
|
|
178
|
+
*/
|
|
179
|
+
async getCacheSize(): Promise<number> {
|
|
180
|
+
if (!this.registration || !this.registration.active) {
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return new Promise((resolve) => {
|
|
185
|
+
const messageChannel = new MessageChannel();
|
|
186
|
+
messageChannel.port1.onmessage = (event) => {
|
|
187
|
+
resolve(event.data.size || 0);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
this.registration!.active!.postMessage(
|
|
191
|
+
{ type: 'GET_CACHE_SIZE' },
|
|
192
|
+
[messageChannel.port2]
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Check if Service Worker is active
|
|
199
|
+
*/
|
|
200
|
+
isActive(): boolean {
|
|
201
|
+
return this.registration !== null && this.registration.active !== null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get Service Worker status
|
|
206
|
+
*/
|
|
207
|
+
getStatus(): 'unsupported' | 'inactive' | 'installing' | 'waiting' | 'active' {
|
|
208
|
+
if (!('serviceWorker' in navigator)) {
|
|
209
|
+
return 'unsupported';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!this.registration) {
|
|
213
|
+
return 'inactive';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (this.registration.installing) {
|
|
217
|
+
return 'installing';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (this.registration.waiting) {
|
|
221
|
+
return 'waiting';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (this.registration.active) {
|
|
225
|
+
return 'active';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return 'inactive';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get the registration
|
|
233
|
+
*/
|
|
234
|
+
getRegistration(): ServiceWorkerRegistration | null {
|
|
235
|
+
return this.registration;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Create and register a Service Worker manager instance
|
|
241
|
+
*/
|
|
242
|
+
export async function registerServiceWorker(
|
|
243
|
+
options?: ServiceWorkerManagerOptions
|
|
244
|
+
): Promise<ServiceWorkerManager> {
|
|
245
|
+
const manager = new ServiceWorkerManager(options);
|
|
246
|
+
await manager.register();
|
|
247
|
+
return manager;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Check if Service Workers are supported
|
|
252
|
+
*/
|
|
253
|
+
export function isServiceWorkerSupported(): boolean {
|
|
254
|
+
return 'serviceWorker' in navigator && window.isSecureContext;
|
|
255
|
+
}
|
package/src/test-deps.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Re-export common dependencies to ensure singleton instances
|
|
2
|
+
import * as Y from "yjs";
|
|
3
|
+
|
|
4
|
+
// Export Yjs parts
|
|
5
|
+
export { Y };
|
|
6
|
+
export * from "yjs";
|
|
7
|
+
|
|
8
|
+
// Export Awareness for y-protocols/awareness mapping
|
|
9
|
+
export * from "y-protocols/awareness";
|
|
10
|
+
|
|
11
|
+
// Export Sync and Auth protocols (manually to avoid collisions)
|
|
12
|
+
export {
|
|
13
|
+
messageYjsSyncStep1,
|
|
14
|
+
messageYjsSyncStep2,
|
|
15
|
+
messageYjsUpdate,
|
|
16
|
+
writeSyncStep1,
|
|
17
|
+
writeSyncStep2,
|
|
18
|
+
writeUpdate,
|
|
19
|
+
readSyncStep1,
|
|
20
|
+
readSyncStep2,
|
|
21
|
+
} from "y-protocols/sync";
|
|
22
|
+
export * from "y-protocols/auth";
|
|
23
|
+
|
|
24
|
+
// Export Tiptap parts
|
|
25
|
+
export { Editor, Extension } from "@tiptap/core";
|
|
26
|
+
export { default as Document } from "@tiptap/extension-document";
|
|
27
|
+
export { default as Paragraph } from "@tiptap/extension-paragraph";
|
|
28
|
+
export { default as Text } from "@tiptap/extension-text";
|
|
29
|
+
export { default as StarterKit } from "@tiptap/starter-kit";
|
|
30
|
+
export { default as Collaboration } from "@tiptap/extension-collaboration";
|
|
31
|
+
export { CollaborationCursor } from "./extensions/collaboration-cursor.js";
|
|
32
|
+
|
|
33
|
+
// Export Hocuspocus Provider
|
|
34
|
+
export * from "@hocuspocus/provider";
|