From a55057db37275466dd898a5a43c26338cbced7e5 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:34:09 +0100 Subject: [PATCH] feat: collab hocuspocus v4 upgrade (#2351) * WIP 1 * complete v4 migration * add flushDelay * feat: multiplexing * fix: survive hocuspocus v4 message timeout * fix: searchTerm type error --- .../src/features/editor/collab-socket.ts | 46 + .../editor/hooks/use-collaboration-url.ts | 7 - .../src/features/editor/page-editor.tsx | 319 +++---- apps/server/package.json | 2 - .../collaboration/collaboration.gateway.ts | 38 +- .../extensions/persistence.extension.ts | 14 +- .../redis-sync/collab-proxy-socket.ts | 54 +- .../redis-sync/redis-sync.extension.ts | 182 ++-- .../extensions/redis-sync/redis-sync.types.ts | 67 +- .../redis-sync/ws-socket-wrapper.ts | 22 +- package.json | 66 +- .../search-and-replace/search-and-replace.ts | 4 +- pnpm-lock.yaml | 842 +++++++++--------- 13 files changed, 876 insertions(+), 787 deletions(-) create mode 100644 apps/client/src/features/editor/collab-socket.ts delete mode 100644 apps/client/src/features/editor/hooks/use-collaboration-url.ts diff --git a/apps/client/src/features/editor/collab-socket.ts b/apps/client/src/features/editor/collab-socket.ts new file mode 100644 index 000000000..de505d242 --- /dev/null +++ b/apps/client/src/features/editor/collab-socket.ts @@ -0,0 +1,46 @@ +import { + HocuspocusProviderWebsocket, + WebSocketStatus, +} from "@hocuspocus/provider"; +import { getCollaborationUrl } from "@/lib/config.ts"; + +const RELEASE_GRACE_MS = 5000; + +let socket: HocuspocusProviderWebsocket | null = null; +let editorCount = 0; +let releaseTimer: ReturnType | null = null; + +export function getCollabSocket(): HocuspocusProviderWebsocket { + if (!socket) { + socket = new HocuspocusProviderWebsocket({ + url: getCollaborationUrl(), + autoConnect: false, + }); + } + return socket; +} + +export function acquireCollabSocket(): void { + editorCount++; + if (releaseTimer) { + clearTimeout(releaseTimer); + releaseTimer = null; + } + const collabSocket = getCollabSocket(); + collabSocket.shouldConnect = true; + if (collabSocket.status === WebSocketStatus.Disconnected) { + collabSocket.connect(); + } +} + +export function releaseCollabSocket(): void { + editorCount--; + if (editorCount > 0) return; + if (releaseTimer) clearTimeout(releaseTimer); + releaseTimer = setTimeout(() => { + releaseTimer = null; + if (editorCount === 0) { + socket?.disconnect(); + } + }, RELEASE_GRACE_MS); +} diff --git a/apps/client/src/features/editor/hooks/use-collaboration-url.ts b/apps/client/src/features/editor/hooks/use-collaboration-url.ts deleted file mode 100644 index 37b78720d..000000000 --- a/apps/client/src/features/editor/hooks/use-collaboration-url.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { getCollaborationUrl } from "@/lib/config.ts"; - -const useCollaborationURL = (): string => { - return getCollaborationUrl(); -}; - -export default useCollaborationURL; diff --git a/apps/client/src/features/editor/page-editor.tsx b/apps/client/src/features/editor/page-editor.tsx index 2b3daa39c..eadcfdd44 100644 --- a/apps/client/src/features/editor/page-editor.tsx +++ b/apps/client/src/features/editor/page-editor.tsx @@ -7,15 +7,16 @@ import React, { useState, } from "react"; import { IndexeddbPersistence } from "y-indexeddb"; -import * as Y from "yjs"; import { - HocuspocusProvider, - onStatusParameters, WebSocketStatus, - HocuspocusProviderWebsocket, - onSyncedParameters, onStatelessParameters, } from "@hocuspocus/provider"; +import { + HocuspocusProviderWebsocketComponent, + HocuspocusRoom, + useHocuspocusEvent, + useHocuspocusProvider, +} from "@hocuspocus/provider-react"; import { Editor, EditorContent, @@ -28,7 +29,6 @@ import { mainExtensions, } from "@/features/editor/extensions/extensions"; import { useAtom, useAtomValue } from "jotai"; -import useCollaborationUrl from "@/features/editor/hooks/use-collaboration-url"; import { currentUserAtom } from "@/features/user/atoms/current-user-atom"; import { currentPageEditModeAtom, @@ -76,6 +76,11 @@ import { EditorLinkMenu } from "@/features/editor/components/link/link-menu"; import ColumnsMenu from "@/features/editor/components/columns/columns-menu.tsx"; import { TransclusionLookupProvider } from "@/features/editor/components/transclusion/transclusion-lookup-context"; import { useTranslation } from "react-i18next"; +import { + acquireCollabSocket, + getCollabSocket, + releaseCollabSocket, +} from "@/features/editor/collab-socket"; interface PageEditorProps { pageId: string; @@ -91,7 +96,78 @@ export default function PageEditor({ canComment, }: PageEditorProps) { const { t } = useTranslation(); - const collaborationURL = useCollaborationUrl(); + const { data: collabQuery, refetch: refetchCollabToken } = useCollabToken(); + const { pageSlug } = useParams(); + const slugId = extractPageSlugId(pageSlug); + const [socket] = useState(getCollabSocket); + + useEffect(() => { + acquireCollabSocket(); + return () => releaseCollabSocket(); + }, []); + + const handleStateless = ({ payload }: onStatelessParameters) => { + try { + const message = JSON.parse(payload); + if (message?.type !== "page.updated" || !message.updatedAt) return; + const pageData = queryClient.getQueryData(["pages", slugId]); + if (pageData) { + queryClient.setQueryData(["pages", slugId], { + ...pageData, + updatedAt: message.updatedAt, + ...(message.lastUpdatedBy && { + lastUpdatedBy: message.lastUpdatedBy, + }), + }); + } + } catch { + // ignore unrelated stateless messages + } + }; + + const handleAuthenticationFailed = () => { + const payload = jwtDecode(collabQuery?.token); + const now = Date.now().valueOf() / 1000; + const isTokenExpired = now >= payload.exp; + if (isTokenExpired) { + refetchCollabToken(); + } + }; + + return ( + + {collabQuery?.token ? ( + + + + + + ) : ( + + )} + + ); +} + +function CollabPageEditor({ + pageId, + editable, + content, + canComment, +}: PageEditorProps) { + const { t } = useTranslation(); + const provider = useHocuspocusProvider(); const isComponentMounted = useRef(false); const editorRef = useRef(null); @@ -112,7 +188,6 @@ export default function PageEditor({ ); const [, setYjsSynced] = useAtom(yjsSyncedAtom); const menuContainerRef = useRef(null); - const { data: collabQuery, refetch: refetchCollabToken } = useCollabToken(); const { isIdle, resetIdle } = useIdle(FIVE_MINUTES, { initialState: false }); const documentState = useDocumentVisibility(); const { pageSlug } = useParams(); @@ -123,95 +198,24 @@ export default function PageEditor({ [isComponentMounted], ); const { handleScrollTo } = useEditorScroll({ canScroll }); - // Providers only created once per pageId - const providersRef = useRef<{ - local: IndexeddbPersistence; - remote: HocuspocusProvider; - socket: HocuspocusProviderWebsocket; - } | null>(null); - const [providersReady, setProvidersReady] = useState(false); useEffect(() => { - if (!providersRef.current) { - const documentName = `page.${pageId}`; - const ydoc = new Y.Doc(); - const local = new IndexeddbPersistence(documentName, ydoc); - const socket = new HocuspocusProviderWebsocket({ - url: collaborationURL, - }); - const onLocalSyncedHandler = () => { - setIsLocalSynced(true); - }; - const onStatusHandler = (event: onStatusParameters) => { - setYjsConnectionStatus(event.status); - }; - const onSyncedHandler = (event: onSyncedParameters) => { - setIsRemoteSynced(event.state); - }; - const onStatelessHandler = ({ payload }: onStatelessParameters) => { - try { - const message = JSON.parse(payload); - if (message?.type !== "page.updated" || !message.updatedAt) return; - const pageData = queryClient.getQueryData(["pages", slugId]); - if (pageData) { - queryClient.setQueryData(["pages", slugId], { - ...pageData, - updatedAt: message.updatedAt, - ...(message.lastUpdatedBy && { - lastUpdatedBy: message.lastUpdatedBy, - }), - }); - } - } catch { - // ignore unrelated stateless messages - } - }; - const onAuthenticationFailedHandler = () => { - const payload = jwtDecode(collabQuery?.token); - const now = Date.now().valueOf() / 1000; - const isTokenExpired = now >= payload.exp; - if (isTokenExpired) { - refetchCollabToken().then((result) => { - if (result.data?.token) { - socket.disconnect(); - setTimeout(() => { - remote.configuration.token = result.data.token; - socket.connect(); - }, 100); - } - }); - } - }; - const remote = new HocuspocusProvider({ - websocketProvider: socket, - name: documentName, - document: ydoc, - token: collabQuery?.token, - onAuthenticationFailed: onAuthenticationFailedHandler, - onStatus: onStatusHandler, - onSynced: onSyncedHandler, - onStateless: onStatelessHandler, - }); - - local.on("synced", onLocalSyncedHandler); - providersRef.current = { socket, local, remote }; - setProvidersReady(true); - } else { - setProvidersReady(true); - } - // Only destroy on final unmount + const local = new IndexeddbPersistence( + provider.configuration.name, + provider.document, + ); + local.on("synced", () => setIsLocalSynced(true)); return () => { - providersRef.current?.socket.destroy(); - providersRef.current?.remote.destroy(); - providersRef.current?.local.destroy(); - providersRef.current = null; + local.destroy(); }; - }, [pageId]); + }, [provider]); + + useHocuspocusEvent("synced", ({ state }) => setIsRemoteSynced(state)); + useHocuspocusEvent("status", ({ status }) => setYjsConnectionStatus(status)); // Only connect/disconnect on tab/idle, not destroy useEffect(() => { - if (!providersReady || !providersRef.current) return; - const socket = providersRef.current.socket; + const socket = provider.configuration.websocketProvider; if ( isIdle && @@ -228,23 +232,15 @@ export default function PageEditor({ resetIdle(); socket.connect(); } - }, [isIdle, documentState, providersReady, resetIdle]); - - // Attach here, to make sure the connection gets properly established - providersRef.current?.remote.attach(); + }, [isIdle, documentState, provider, resetIdle]); const extensions = useMemo(() => { - if (!providersReady || !providersRef.current || !currentUser?.user) { + if (!currentUser?.user) { return mainExtensions; } - const remoteProvider = providersRef.current.remote; - - return [ - ...mainExtensions, - ...collabExtensions(remoteProvider, currentUser?.user), - ]; - }, [providersReady, currentUser?.user]); + return [...mainExtensions, ...collabExtensions(provider, currentUser.user)]; + }, [provider, currentUser?.user]); const editor = useEditor( { @@ -416,65 +412,72 @@ export default function PageEditor({ } }, [yjsConnectionStatus, isSynced]); + if (showStatic) { + return ; + } + return ( - - {showStatic ? ( - - ) : ( -
-
- +
+
+ - {editor && ( - - )} + {editor && ( + + )} - {editor && editorIsEditable && ( -
- - - - - - - - - - - - - -
- )} - {editor && - !editorIsEditable && - (editable || canComment) && - providersRef.current && } - {showCommentPopup && ( - - )} - {showReadOnlyCommentPopup && ( - - )} + {editor && editorIsEditable && ( +
+ + + + + + + + + + + + +
-
{ - if (editor && !editor.isDestroyed) editor.commands.focus("end"); - }} - style={{ paddingBottom: "20vh" }} - >
-
- )} - + )} + {editor && !editorIsEditable && (editable || canComment) && ( + + )} + {showCommentPopup && } + {showReadOnlyCommentPopup && ( + + )} +
+
{ + if (editor && !editor.isDestroyed) editor.commands.focus("end"); + }} + style={{ paddingBottom: "20vh" }} + >
+
+ ); +} + +function StaticPageEditor({ + content, + ariaLabel, +}: { + content: any; + ariaLabel: string; +}) { + return ( + ); } diff --git a/apps/server/package.json b/apps/server/package.json index 5d178be92..4cd625acc 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -88,7 +88,6 @@ "kysely-migration-cli": "0.4.2", "kysely-postgres-js": "3.0.0", "ldapts": "8.1.7", - "lib0": "0.2.117", "mammoth": "1.12.0", "mime-types": "3.0.2", "msgpackr": "^1.11.9", @@ -118,7 +117,6 @@ "stripe": "^17.7.0", "tlds": "1.261.0", "tmp-promise": "3.0.3", - "tseep": "1.3.1", "typesense": "3.0.5", "undici": "7.28.0", "ws": "8.21.0", diff --git a/apps/server/src/collaboration/collaboration.gateway.ts b/apps/server/src/collaboration/collaboration.gateway.ts index b46c13c8a..b799ce366 100644 --- a/apps/server/src/collaboration/collaboration.gateway.ts +++ b/apps/server/src/collaboration/collaboration.gateway.ts @@ -15,6 +15,7 @@ import { RedisSyncExtension, SerializedHTTPRequest, } from './extensions/redis-sync'; +import { toWebRequest } from './extensions/redis-sync/redis-sync.types'; import { WsSocketWrapper } from './extensions/redis-sync/ws-socket-wrapper'; import RedisClient from 'ioredis'; import { pack, unpack } from 'msgpackr'; @@ -98,34 +99,36 @@ export class CollaborationGateway { const serializedHTTPRequest = this.serializeRequest(request); const socketId = serializedHTTPRequest.headers['sec-websocket-key']; - // Create wrapper socket that only receives events via emit() - // This prevents double-handling since Hocuspocus won't listen to raw WebSocket events const wrappedSocket = new WsSocketWrapper(client); // Route through RedisSync extension (this calls handleConnection internally) - this.redisSync.onSocketOpen(wrappedSocket as any, serializedHTTPRequest); + this.redisSync.onSocketOpen(wrappedSocket, serializedHTTPRequest); - // Forward raw WebSocket messages to the extension client.on('message', (data: ArrayBuffer) => { - this.redisSync!.onSocketMessage( - wrappedSocket as any, - serializedHTTPRequest, - data, - ); + this.redisSync!.onSocketMessage(serializedHTTPRequest, data); }); - // Forward close events client.on('close', (code: number, reason: Buffer) => { - this.redisSync!.onSocketClose(socketId, code, reason.buffer as ArrayBuffer); - }); - - // Forward pong events for keepalive - client.on('pong', (data: Buffer) => { - wrappedSocket.emit('pong', data); + this.redisSync!.onSocketClose( + socketId, + code, + new Uint8Array(reason).buffer, + ); }); } else { // Fallback to direct Hocuspocus connection - this.hocuspocus.handleConnection(client, request); + const clientConnection = this.hocuspocus.handleConnection( + client, + toWebRequest(this.serializeRequest(request)), + ); + + client.on('message', (data: Buffer) => { + clientConnection.handleMessage(new Uint8Array(data)); + }); + + client.on('close', (code: number, reason: Buffer) => { + clientConnection.handleClose({ code, reason: reason.toString() }); + }); } } @@ -178,6 +181,7 @@ export class CollaborationGateway { if (this.hocuspocus.getDocumentsCount() === 0) resolve(''); this.hocuspocus.closeConnections(); + this.hocuspocus.flushPendingStores(); } catch (error) { console.error(error); } diff --git a/apps/server/src/collaboration/extensions/persistence.extension.ts b/apps/server/src/collaboration/extensions/persistence.extension.ts index 3a4df24a7..85efb5ec4 100644 --- a/apps/server/src/collaboration/extensions/persistence.extension.ts +++ b/apps/server/src/collaboration/extensions/persistence.extension.ts @@ -96,7 +96,7 @@ export class PersistenceExtension implements Extension { } async onStoreDocument(data: onStoreDocumentPayload) { - const { documentName, document, context } = data; + const { documentName, document, lastContext } = data; const pageId = getPageId(documentName); @@ -151,7 +151,7 @@ export class PersistenceExtension implements Extension { content: tiptapJson, textContent: textContent, ydoc: ydocState, - lastUpdatedById: context.user.id, + lastUpdatedById: lastContext.user.id, contributorIds: contributorIds, }, pageId, @@ -169,12 +169,12 @@ export class PersistenceExtension implements Extension { JSON.stringify({ type: 'page.updated', updatedAt: new Date().toISOString(), - lastUpdatedById: context?.user?.id, - lastUpdatedBy: context?.user + lastUpdatedById: lastContext?.user?.id, + lastUpdatedBy: lastContext?.user ? { - id: context.user?.id, - name: context.user?.name, - avatarUrl: context.user?.avatarUrl, + id: lastContext.user?.id, + name: lastContext.user?.name, + avatarUrl: lastContext.user?.avatarUrl, } : undefined, }), diff --git a/apps/server/src/collaboration/extensions/redis-sync/collab-proxy-socket.ts b/apps/server/src/collaboration/extensions/redis-sync/collab-proxy-socket.ts index 8ecfa00af..a75c0773c 100644 --- a/apps/server/src/collaboration/extensions/redis-sync/collab-proxy-socket.ts +++ b/apps/server/src/collaboration/extensions/redis-sync/collab-proxy-socket.ts @@ -1,61 +1,37 @@ import type RedisClient from 'ioredis'; -import { EventEmitter } from 'tseep'; -import type { - Pack, - RSAMessageClose, - RSAMessagePing, - RSAMessageSend, -} from './redis-sync.types'; +import type { WebSocketLike } from '@hocuspocus/server'; +import type { Pack, RSAMessageClose, RSAMessageSend } from './redis-sync.types'; -export class CollabProxySocket extends EventEmitter { +// Stands in for the client WebSocket on the server that owns the document. +// Outgoing traffic is relayed over redis to the origin server, which holds the real socket. +export class CollabProxySocket implements WebSocketLike { private readonly replyTo: string; - private readonly serverChannel: string; private readonly socketId: string; private pub: RedisClient; private readonly pack: Pack; readyState = 1; + onClose?: (code?: number, reason?: string) => void; - constructor( - pub: RedisClient, - pack: Pack, - replyTo: string, - serverChannel: string, - socketId: string, - ) { - super(); + constructor(pub: RedisClient, pack: Pack, replyTo: string, socketId: string) { this.replyTo = replyTo; this.socketId = socketId; - this.serverChannel = serverChannel; this.pub = pub; this.pack = pack; - this.once('close', () => { - this.readyState = 3; - }); } - private publish(msg: RSAMessageClose | RSAMessagePing | RSAMessageSend) { + private publish(msg: RSAMessageClose | RSAMessageSend) { this.pub.publish(this.replyTo, this.pack(msg)); } + // The origin server already closed the real socket; stop relaying without echoing a close back + markClosed() { + this.readyState = 3; + } + close(code?: number, reason?: string) { if (this.readyState !== 1) return; - const msg: RSAMessageClose = { - type: 'close', - code, - reason, - socketId: this.socketId, - }; - this.publish(msg); - } - - ping() { - if (this.readyState !== 1) return; - const msg: RSAMessagePing = { - type: 'ping', - socketId: this.socketId, - replyTo: this.serverChannel, - }; - this.publish(msg); + this.readyState = 3; + this.onClose?.(code, reason); } send(message: Uint8Array) { diff --git a/apps/server/src/collaboration/extensions/redis-sync/redis-sync.extension.ts b/apps/server/src/collaboration/extensions/redis-sync/redis-sync.extension.ts index 38747465e..42139097f 100644 --- a/apps/server/src/collaboration/extensions/redis-sync/redis-sync.extension.ts +++ b/apps/server/src/collaboration/extensions/redis-sync/redis-sync.extension.ts @@ -3,27 +3,30 @@ import { Extension, Hocuspocus, IncomingMessage, - afterUnloadDocumentPayload, onConfigurePayload, onLoadDocumentPayload, + afterUnloadDocumentPayload, + WebSocketLike, } from '@hocuspocus/server'; +import { ConnectionTimeout, Unauthorized } from '@hocuspocus/common'; import RedisClient from 'ioredis'; -import { readVarString } from 'lib0/decoding.js'; import { CollabProxySocket } from './collab-proxy-socket'; import { - BaseWebSocket, Configuration, CustomEvents, Pack, RSAMessage, + RSAMessageClose, RSAMessageCloseProxy, RSAMessageCustomEventComplete, RSAMessageCustomEventStart, - RSAMessagePong, RSAMessageProxy, RSAMessageUnload, SerializedHTTPRequest, Unpack, + OriginConnection, + ProxyConnection, + toWebRequest, } from './redis-sync.types'; export type { Pack, SerializedHTTPRequest } from './redis-sync.types'; @@ -38,10 +41,10 @@ export class RedisSyncExtension implements Extension { private sub: RedisClient; private readonly pack: Pack; private readonly unpack: Unpack; - private originSockets: Record = {}; + private originConnections: Record = {}; private locks: Record = {}; private lockPromises: Record> = {}; - private proxySockets: Record = {}; + private proxyConnections: Record = {}; private readonly prefix: string; private readonly lockPrefix: string; private readonly msgChannel: string; @@ -54,6 +57,9 @@ export class RedisSyncExtension implements Extension { // @ts-ignore private pendingReplies: Record['resolve']> = {}; + private deriveContext: ( + serializedHTTPRequest: SerializedHTTPRequest, + ) => Record; constructor(configuration: Configuration) { const { @@ -65,6 +71,7 @@ export class RedisSyncExtension implements Extension { prefix, customEvents, customEventTTL, + deriveContext, } = configuration; this.pub = redis.duplicate(); this.sub = redis.duplicate(); @@ -77,6 +84,7 @@ export class RedisSyncExtension implements Extension { this.lockPrefix = `${this.prefix}Lock`; this.msgChannel = `${this.prefix}Msg`; this.customEvents = (customEvents as any) ?? ({} as any as CustomEvents); + this.deriveContext = deriveContext ?? (() => ({})); this.sub.subscribe(this.msgChannel, `${this.msgChannel}:${this.serverId}`); this.sub.on('messageBuffer', this.handleRedisMessage); this.pub.on('error', () => {}); @@ -87,44 +95,63 @@ export class RedisSyncExtension implements Extension { } private closeProxy(socketId: string) { - const proxySocket = this.proxySockets[socketId]; - if (proxySocket) { - proxySocket.emit( - 'close', - 1000, - Buffer.from('provider_initiated', 'utf-8'), - ); - delete this.proxySockets[socketId]; + const entry = this.proxyConnections[socketId]; + if (entry) { + delete this.proxyConnections[socketId]; + const { socket, clientConnection } = entry; + // The origin socket is already gone; don't echo a close message back + socket.markClosed(); + clientConnection.handleClose({ + code: 1000, + reason: 'provider_initiated', + }); } } - private pongProxy(socketId: string) { - this.proxySockets[socketId]?.emit('pong'); - } - private handleProxyMessage( msg: Pick, ) { const { replyTo, message, serializedHTTPRequest } = msg; const { headers } = serializedHTTPRequest; - const socketId = headers['sec-websocket-key']!; - let socket = this.proxySockets[socketId]; - if (!socket) { - socket = new CollabProxySocket( + const socketId = headers['sec-websocket-key']; + let entry = this.proxyConnections[socketId]; + if (!entry) { + const socket = new CollabProxySocket( this.pub, this.pack, replyTo, - `${this.msgChannel}:${this.serverId}`, socketId, ); - this.proxySockets[socketId] = socket; - this.instance.handleConnection( - socket as any, - serializedHTTPRequest as any, - {}, + // A proxy connection with no live documents (client left the page, auth + // failed, or the origin server crashed) is reaped by hocuspocus' message + // timeout. Dispose it silently in that case: relaying the timeout close + // to the origin would kill the client's real socket, which may be busy + // serving other documents. Genuine protocol closes are still relayed. + socket.onClose = (code, reason) => { + delete this.proxyConnections[socketId]; + if (code !== ConnectionTimeout.code) { + const msg: RSAMessageClose = { + type: 'close', + code, + reason, + socketId, + }; + this.pub.publish(replyTo, this.pack(msg)); + } + }; + const clientConnection = this.instance.handleConnection( + socket, + toWebRequest(serializedHTTPRequest), + this.deriveContext(serializedHTTPRequest), ); + entry = { clientConnection, socket }; + this.proxyConnections[socketId] = entry; } - socket.emit('message', message); + entry.clientConnection.handleMessage(message); + } + + private getLock(documentName: string) { + return this.pub.get(this.getKey(documentName)); } private getOrClaimLock(documentName: string) { @@ -166,10 +193,6 @@ export class RedisSyncExtension implements Extension { this.closeProxy(msg.socketId); return; } - if (type === 'pong') { - this.pongProxy(msg.socketId); - return; - } if (type === 'unload') { delete this.lockPromises[msg.documentName]; return; @@ -198,22 +221,14 @@ export class RedisSyncExtension implements Extension { return; } const { socketId } = msg; - const socket = this.originSockets[socketId]; - if (!socket) { + const entry = this.originConnections[socketId]; + if (!entry) { // origin socket already cleaned up return; } + const { socket } = entry; if (type === 'close') { socket.close(msg.code, msg.reason); - } else if (type === 'ping') { - // Reply instantly to the proxy socket, without forwarding to client - // The origin socket handles heartbeat for itself - const { replyTo, socketId } = msg; - const reply: RSAMessagePong = { - type: 'pong', - socketId, - }; - this.pub.publish(`${replyTo}`, this.pack(reply)); } else if (type === 'send') { socket.send(msg.message); } @@ -251,6 +266,8 @@ export class RedisSyncExtension implements Extension { eventName: TName, documentName: string, payload: any, + // if true, don't claim the lock. Useful for targeting pages that are currently open + onlyIfOpen = false, ) { const isDocLoadedOnInstance = this.instance.documents.has(documentName); @@ -258,7 +275,14 @@ export class RedisSyncExtension implements Extension { return this.handleEventLocally(eventName, documentName, payload); } - const proxyTo = await this.getOrClaimLockThrottled(documentName); + const proxyTo = await (onlyIfOpen + ? this.getLock(documentName) + : this.getOrClaimLockThrottled(documentName)); + + if (!proxyTo && onlyIfOpen) { + return; + } + if (proxyTo && proxyTo !== this.serverId) { ++this.replyIdCounter; // bug in biome thinks this.replyIdCounter is not used if written on the line below const replyId = this.replyIdCounter; @@ -277,7 +301,8 @@ export class RedisSyncExtension implements Extension { const { promise, resolve, reject } = Promise.withResolvers(); this.pendingReplies[replyId] = resolve; setTimeout(() => { - reject('TIMEOUT'); + delete this.pendingReplies[replyId]; + reject(new Error('TIMEOUT')); }, this.customEventTTL); return promise as Promise>; } @@ -296,36 +321,59 @@ export class RedisSyncExtension implements Extension { /* WebSocket Server Hooks */ onSocketOpen( - ws: BaseWebSocket, + ws: WebSocketLike, serializedHTTPRequest: SerializedHTTPRequest, - context = {}, ) { - const socketId = serializedHTTPRequest.headers['sec-websocket-key']!; - this.originSockets[socketId] = ws; - this.instance.handleConnection( - ws as any, - serializedHTTPRequest as any, - context, + const socketId = serializedHTTPRequest.headers['sec-websocket-key']; + const clientConnection = this.instance.handleConnection( + ws, + toWebRequest(serializedHTTPRequest), + this.deriveContext(serializedHTTPRequest), ); + this.originConnections[socketId] = { clientConnection, socket: ws }; } async onSocketMessage( - ws: BaseWebSocket, serializedHTTPRequest: SerializedHTTPRequest, detachableMsg: ArrayBuffer, ) { - const message = new Uint8Array(detachableMsg.slice()); - const tmpMsg = new IncomingMessage(detachableMsg); - const documentName = readVarString(tmpMsg.decoder); + const socketId = serializedHTTPRequest.headers['sec-websocket-key']; + const entry = this.originConnections[socketId]; + if (!entry) return; + const { clientConnection } = entry; + + let message: Uint8Array; + let documentName: string; + try { + message = new Uint8Array(detachableMsg.slice()); + const tmpMsg = new IncomingMessage(detachableMsg); + const documentNameAndSessionId = tmpMsg.readVarString(); + // session-aware providers suffix the documentName with \0sessionId + const sepIdx = documentNameAndSessionId.indexOf('\0'); + documentName = + sepIdx === -1 + ? documentNameAndSessionId + : documentNameAndSessionId.slice(0, sepIdx); + } catch (error) { + entry.socket.close(Unauthorized.code, Unauthorized.reason); + return; + } const isDocLoadedOnInstance = this.instance.documents.has(documentName); if (isDocLoadedOnInstance) { - ws.emit('message', message); + clientConnection.handleMessage(message); return; } const proxyTo = await this.getOrClaimLockThrottled(documentName); if (proxyTo && proxyTo !== this.serverId) { + // Proxied messages bypass handleMessage, so refresh the connection's + // liveness fields manually or hocuspocus' message timeout would reap the + // real socket every `timeout` ms. connectionEstablishedAt is the + // reference while unauthenticated (auth for remote docs is proxied too) + // and is private upstream. + clientConnection.lastMessageReceivedAt = Date.now(); + (clientConnection as any).connectionEstablishedAt = Date.now(); // another server owns the doc const proxyMessage: RSAMessageProxy = { serializedHTTPRequest: serializedHTTPRequest, @@ -338,16 +386,17 @@ export class RedisSyncExtension implements Extension { return; } // This server owns the document, but hocuspocus hasn't loaded it yet - ws.emit('message', message); + clientConnection.handleMessage(message); } onSocketClose(socketId: string, code?: number, reason?: ArrayBuffer) { - const socket = this.originSockets[socketId]; - if (!socket) return; - // at this point the socket is considered GC'd and we cannot call close - // The origin socket did not set up any connections for the proxy, so none of the hooks will work if we just emit - socket?.emit('close', code, reason); - delete this.originSockets[socketId]; + const entry = this.originConnections[socketId]; + if (!entry) return; + delete this.originConnections[socketId]; + entry.clientConnection.handleClose({ + code: code ?? 1000, + reason: reason ? Buffer.from(reason).toString() : '', + }); const msg: RSAMessageCloseProxy = { type: 'closeProxy', socketId }; this.pub.publish(this.msgChannel, this.pack(msg)).catch(() => {}); } @@ -372,6 +421,7 @@ export class RedisSyncExtension implements Extension { } async onDestroy() { + this.pendingReplies = {}; this.pub.disconnect(false); this.sub.disconnect(false); } diff --git a/apps/server/src/collaboration/extensions/redis-sync/redis-sync.types.ts b/apps/server/src/collaboration/extensions/redis-sync/redis-sync.types.ts index 1bbab80a2..1927e3b2b 100644 --- a/apps/server/src/collaboration/extensions/redis-sync/redis-sync.types.ts +++ b/apps/server/src/collaboration/extensions/redis-sync/redis-sync.types.ts @@ -1,12 +1,13 @@ -import EventEmitter from 'node:events'; import { IncomingHttpHeaders } from 'node:http2'; import RedisClient from 'ioredis'; +import { CollabProxySocket } from './collab-proxy-socket'; +import { type Hocuspocus, type WebSocketLike } from '@hocuspocus/server'; export type SecondParam = T extends ( - arg1: unknown, + arg1: any, arg2: infer A, - ...args: unknown[] -) => unknown + ...args: any[] +) => any ? A : never; @@ -41,17 +42,6 @@ export type RSAMessageClose = { socketId: string; }; -export type RSAMessagePing = { - type: 'ping'; - socketId: string; - replyTo: string; -}; - -export type RSAMessagePong = { - type: 'pong'; - socketId: string; -}; - export type RSAMessageSend = { type: 'send'; // @ts-ignore @@ -59,7 +49,7 @@ export type RSAMessageSend = { socketId: string; }; -export type RSAMessageCustomEventStart = { +export type RSAMessageCustomEventStart = { type: 'customEventStart'; documentName: string; eventName: TName; @@ -71,7 +61,7 @@ export type RSAMessageCustomEventStart = { export type RSAMessageCustomEventComplete = { type: 'customEventComplete'; replyId: number; - payload: unknown; + payload: any; }; export type RSAMessage = @@ -79,8 +69,6 @@ export type RSAMessage = | RSAMessageCloseProxy | RSAMessageUnload | RSAMessageClose - | RSAMessagePing - | RSAMessagePong | RSAMessageSend | RSAMessageCustomEventStart | RSAMessageCustomEventComplete; @@ -99,9 +87,20 @@ type CustomEventName = string; export type CustomEvents = Record< CustomEventName, - (documentName: string, payload: unknown) => Promise + (documentName: string, payload: any) => Promise >; +// Not exported by @hocuspocus/server +export type ClientConnection = ReturnType; +export type OriginConnection = { + clientConnection: ClientConnection; + socket: WebSocketLike; +}; +export type ProxyConnection = { + clientConnection: ClientConnection; + socket: CollabProxySocket; +}; + export interface Configuration { redis: RedisClient; pack: Pack; @@ -111,11 +110,29 @@ export interface Configuration { customEventTTL?: number; prefix?: string; customEvents?: TCE; + // Derive the hocuspocus context once per socket instead of re-deriving it in a + // per-document hook like onConnect/onAuthenticate. Runs on the origin server when + // the socket opens and on the doc owner when the first proxied message arrives. + deriveContext?: ( + serializedHTTPRequest: SerializedHTTPRequest, + ) => Record; } -export type BaseWebSocket = EventEmitter & { - readyState: number; - close(code?: number, reason?: string): void; - ping(): void; - send(message: Uint8Array): void; +// Hocuspocus expects a web-standard Request, so rehydrate one from what crossed the wire +export const toWebRequest = (serializedHTTPRequest: SerializedHTTPRequest) => { + const { method, url, headers } = serializedHTTPRequest; + const webHeaders = new Headers(); + Object.entries(headers).forEach(([name, value]) => { + if (Array.isArray(value)) { + value.forEach((v) => { + webHeaders.append(name, v); + }); + } else if (value !== undefined) { + webHeaders.set(name, value); + } + }); + return new Request(new URL(url, 'http://localhost'), { + method, + headers: webHeaders, + }); }; diff --git a/apps/server/src/collaboration/extensions/redis-sync/ws-socket-wrapper.ts b/apps/server/src/collaboration/extensions/redis-sync/ws-socket-wrapper.ts index 258e6e12f..0ce14ed1d 100644 --- a/apps/server/src/collaboration/extensions/redis-sync/ws-socket-wrapper.ts +++ b/apps/server/src/collaboration/extensions/redis-sync/ws-socket-wrapper.ts @@ -1,20 +1,17 @@ -import { EventEmitter } from 'events'; import type WebSocket from 'ws'; +import type { WebSocketLike } from '@hocuspocus/server'; /** - * Wrapper around ws WebSocket that only receives events via emit(). - * This prevents double-handling when used with RedisSyncExtension. + * Wrapper around ws WebSocket that Hocuspocus only writes to. + * Incoming socket events are forwarded separately by the gateway, + * which prevents double-handling with RedisSyncExtension. */ -export class WsSocketWrapper extends EventEmitter { +export class WsSocketWrapper implements WebSocketLike { private ws: WebSocket; readyState = 1; constructor(ws: WebSocket) { - super(); this.ws = ws; - this.once('close', () => { - this.readyState = 3; - }); } close(code?: number, reason?: string) { @@ -27,15 +24,6 @@ export class WsSocketWrapper extends EventEmitter { } } - ping() { - if (this.readyState !== 1) return; - try { - this.ws.ping(); - } catch (e) { - // Socket already closed - } - } - send(message: Uint8Array) { if (this.readyState !== 1) return; try { diff --git a/package.json b/package.json index 2250548be..e0f540a93 100644 --- a/package.json +++ b/package.json @@ -23,41 +23,43 @@ "@casl/ability": "6.8.0", "@docmost/editor-ext": "workspace:*", "@floating-ui/dom": "1.7.3", - "@hocuspocus/provider": "3.4.4", - "@hocuspocus/server": "3.4.4", - "@hocuspocus/transformer": "3.4.4", + "@hocuspocus/common": "4.4.0", + "@hocuspocus/provider": "4.4.0", + "@hocuspocus/provider-react": "4.4.0", + "@hocuspocus/server": "4.4.0", + "@hocuspocus/transformer": "4.4.0", "@joplin/turndown": "4.0.82", "@joplin/turndown-plugin-gfm": "1.0.64", "@sindresorhus/slugify": "3.0.0", - "@tiptap/core": "3.27.1", - "@tiptap/extension-audio": "3.27.1", - "@tiptap/extension-code-block": "3.27.1", - "@tiptap/extension-collaboration": "3.27.1", - "@tiptap/extension-collaboration-caret": "3.27.1", - "@tiptap/extension-color": "3.27.1", - "@tiptap/extension-document": "3.27.1", - "@tiptap/extension-heading": "3.27.1", - "@tiptap/extension-highlight": "3.27.1", - "@tiptap/extension-history": "3.27.1", - "@tiptap/extension-image": "3.27.1", - "@tiptap/extension-link": "3.27.1", - "@tiptap/extension-list": "3.27.1", - "@tiptap/extension-placeholder": "3.27.1", - "@tiptap/extension-subscript": "3.27.1", - "@tiptap/extension-superscript": "3.27.1", - "@tiptap/extension-table": "3.27.1", - "@tiptap/extension-text": "3.27.1", - "@tiptap/extension-text-align": "3.27.1", - "@tiptap/extension-text-style": "3.27.1", - "@tiptap/extension-typography": "3.27.1", - "@tiptap/extension-unique-id": "3.27.1", - "@tiptap/extension-youtube": "3.27.1", - "@tiptap/html": "3.27.1", - "@tiptap/pm": "3.27.1", - "@tiptap/react": "3.27.1", - "@tiptap/starter-kit": "3.27.1", - "@tiptap/suggestion": "3.27.1", - "@tiptap/y-tiptap": "3.0.5", + "@tiptap/core": "3.28.0", + "@tiptap/extension-audio": "3.28.0", + "@tiptap/extension-code-block": "3.28.0", + "@tiptap/extension-collaboration": "3.28.0", + "@tiptap/extension-collaboration-caret": "3.28.0", + "@tiptap/extension-color": "3.28.0", + "@tiptap/extension-document": "3.28.0", + "@tiptap/extension-heading": "3.28.0", + "@tiptap/extension-highlight": "3.28.0", + "@tiptap/extension-history": "3.28.0", + "@tiptap/extension-image": "3.28.0", + "@tiptap/extension-link": "3.28.0", + "@tiptap/extension-list": "3.28.0", + "@tiptap/extension-placeholder": "3.28.0", + "@tiptap/extension-subscript": "3.28.0", + "@tiptap/extension-superscript": "3.28.0", + "@tiptap/extension-table": "3.28.0", + "@tiptap/extension-text": "3.28.0", + "@tiptap/extension-text-align": "3.28.0", + "@tiptap/extension-text-style": "3.28.0", + "@tiptap/extension-typography": "3.28.0", + "@tiptap/extension-unique-id": "3.28.0", + "@tiptap/extension-youtube": "3.28.0", + "@tiptap/html": "3.28.0", + "@tiptap/pm": "3.28.0", + "@tiptap/react": "3.28.0", + "@tiptap/starter-kit": "3.28.0", + "@tiptap/suggestion": "3.28.0", + "@tiptap/y-tiptap": "3.0.7", "bytes": "3.1.2", "cross-env": "10.1.0", "date-fns": "4.1.0", diff --git a/packages/editor-ext/src/lib/search-and-replace/search-and-replace.ts b/packages/editor-ext/src/lib/search-and-replace/search-and-replace.ts index 2326a50a9..1e77732a6 100644 --- a/packages/editor-ext/src/lib/search-and-replace/search-and-replace.ts +++ b/packages/editor-ext/src/lib/search-and-replace/search-and-replace.ts @@ -422,6 +422,8 @@ export const SearchAndReplace = Extension.create< state: { init: () => DecorationSet.empty, apply({ doc, docChanged }, oldState) { + const storage = editor.storage.searchAndReplace; + if (!storage) return oldState; const { searchTerm, lastSearchTerm, @@ -429,7 +431,7 @@ export const SearchAndReplace = Extension.create< lastCaseSensitive, resultIndex, lastResultIndex, - } = editor.storage.searchAndReplace; + } = storage; if ( !docChanged && diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61b00b924..27cb86d6e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,15 +74,21 @@ importers: '@floating-ui/dom': specifier: 1.7.3 version: 1.7.3 + '@hocuspocus/common': + specifier: 4.4.0 + version: 4.4.0 '@hocuspocus/provider': - specifier: 3.4.4 - version: 3.4.4(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) + specifier: 4.4.0 + version: 4.4.0(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) + '@hocuspocus/provider-react': + specifier: 4.4.0 + version: 4.4.0(@hocuspocus/provider@4.4.0(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(react@19.2.7)(yjs@13.6.30) '@hocuspocus/server': - specifier: 3.4.4 - version: 3.4.4(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) + specifier: 4.4.0 + version: 4.4.0(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) '@hocuspocus/transformer': - specifier: 3.4.4 - version: 3.4.4(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(y-prosemirror@1.3.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30) + specifier: 4.4.0 + version: 4.4.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(y-prosemirror@1.3.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30) '@joplin/turndown': specifier: 4.0.82 version: 4.0.82 @@ -93,92 +99,92 @@ importers: specifier: 3.0.0 version: 3.0.0 '@tiptap/core': - specifier: 3.27.1 - version: 3.27.1(@tiptap/pm@3.27.1) + specifier: 3.28.0 + version: 3.28.0(@tiptap/pm@3.28.0) '@tiptap/extension-audio': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) '@tiptap/extension-code-block': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) '@tiptap/extension-collaboration': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(@tiptap/y-tiptap@3.0.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30) '@tiptap/extension-collaboration-caret': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(@tiptap/y-tiptap@3.0.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)) '@tiptap/extension-color': - specifier: 3.27.1 - version: 3.27.1(@tiptap/extension-text-style@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))) + specifier: 3.28.0 + version: 3.28.0(@tiptap/extension-text-style@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))) '@tiptap/extension-document': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) '@tiptap/extension-heading': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) '@tiptap/extension-highlight': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) '@tiptap/extension-history': - specifier: 3.27.1 - version: 3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) '@tiptap/extension-image': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) '@tiptap/extension-link': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) '@tiptap/extension-list': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) '@tiptap/extension-placeholder': - specifier: 3.27.1 - version: 3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) '@tiptap/extension-subscript': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) '@tiptap/extension-superscript': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) '@tiptap/extension-table': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) '@tiptap/extension-text': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) '@tiptap/extension-text-align': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) '@tiptap/extension-text-style': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) '@tiptap/extension-typography': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) '@tiptap/extension-unique-id': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) '@tiptap/extension-youtube': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) '@tiptap/html': - specifier: 3.27.1 - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(happy-dom@20.8.9) + specifier: 3.28.0 + version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(happy-dom@20.8.9) '@tiptap/pm': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.28.0 + version: 3.28.0 '@tiptap/react': - specifier: 3.27.1 - version: 3.27.1(@floating-ui/dom@1.7.3)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 3.28.0 + version: 3.28.0(@floating-ui/dom@1.7.3)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@tiptap/starter-kit': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.28.0 + version: 3.28.0 '@tiptap/suggestion': - specifier: 3.27.1 - version: 3.27.1(@floating-ui/dom@1.7.3)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + specifier: 3.28.0 + version: 3.28.0(@floating-ui/dom@1.7.3)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) '@tiptap/y-tiptap': - specifier: 3.0.5 - version: 3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) + specifier: 3.0.7 + version: 3.0.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) bytes: specifier: 3.1.2 version: 3.1.2 @@ -679,9 +685,6 @@ importers: ldapts: specifier: 8.1.7 version: 8.1.7 - lib0: - specifier: 0.2.117 - version: 0.2.117 mammoth: specifier: 1.12.0 version: 1.12.0 @@ -769,9 +772,6 @@ importers: tmp-promise: specifier: 3.0.3 version: 3.0.3 - tseep: - specifier: 1.3.1 - version: 1.3.1 typesense: specifier: 3.0.5 version: 3.0.5(@babel/runtime@7.29.2) @@ -2347,23 +2347,31 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - '@hocuspocus/common@3.4.4': - resolution: {integrity: sha512-RykIJ0tsHHMP4Xk+4UCbc7SO5LgGxGUSTdbh6anJEsaALAyqinf1Nn5HYuMjLPolAmsar1v++m9zufR09NLpXA==} + '@hocuspocus/common@4.4.0': + resolution: {integrity: sha512-cPSlPu/ws2JzBHW6OzctyaQMyDCq/YUS4nhXSbyMdkQwJzhjCkvfmmRnVtcmgLR3TBLaNwToNDwR/mNaJFOYsg==} - '@hocuspocus/provider@3.4.4': - resolution: {integrity: sha512-KbsMAfdYcIJD8eMU/5QnpXcSOvIWAcCNI33FSRSaKCIpYBFtAwkYIwWnZJmPZ8a1BMAtqQc+uvy9+UQf7GHnGQ==} + '@hocuspocus/provider-react@4.4.0': + resolution: {integrity: sha512-8rbhIYQUl8ZvE5q4SHFo9+oYjFCkVoWssQJV6q5CMNuV7D/v1lJoQZLpznw7vzWWj9ijVrqNiI8UFnc2X8AGZg==} + peerDependencies: + '@hocuspocus/provider': ^4.4.0 + react: ^18.0.0 || ^19.0.0 + yjs: ^13.6.8 + + '@hocuspocus/provider@4.4.0': + resolution: {integrity: sha512-A83ROMFqU2bgjTTQ1YMteY9v2cIhDHj4S6XgzjPaqYEgOFr2XvBYGnK+NXzZ6DgldKTnlN7qqvWvZplPvltgyw==} peerDependencies: y-protocols: ^1.0.6 yjs: ^13.6.8 - '@hocuspocus/server@3.4.4': - resolution: {integrity: sha512-UV+oaONAejOzeYgUygNcgsc8RdZvSokVvAxluZJIisLACpRO/VsseQ5lWKDRwLd7Fn6+rHWDH3hGuQ1fdX1Ycg==} + '@hocuspocus/server@4.4.0': + resolution: {integrity: sha512-eVSnx+76CN81vaRol+OlT8FyyGFOBF9Z+kdJQhjVXRpj3kIAI1TzuRHKiZN1vkUogdHfmOAN5Bhs5v7V/qhMkQ==} + engines: {node: '>=22'} peerDependencies: y-protocols: ^1.0.6 yjs: ^13.6.8 - '@hocuspocus/transformer@3.4.4': - resolution: {integrity: sha512-X0EJ863LV97YbL5m8WTt4NDSC6uHi6ZCq/teIH5aholdjdhdTmFrzMemdhha/ZqPUZyaKhOoAmZzwR55HZLPpQ==} + '@hocuspocus/transformer@4.4.0': + resolution: {integrity: sha512-wvHgbiWfU1QERIMd37aImtb4xbHRGwek+66VPgFaSx3POgxCKmkZH5iVY25j/T0PBmloofPoV1XYbwLwJZMtJw==} peerDependencies: '@tiptap/core': ^3.0.1 '@tiptap/pm': ^3.0.1 @@ -4268,260 +4276,261 @@ packages: '@types/react-dom': optional: true - '@tiptap/core@3.27.1': - resolution: {integrity: sha512-rV6Qn4wmC6BxfF+4mu6bqGWj9vA4oXXhsrpXaJL2uhjxeHAGofjwcHof2X84VYzeyXgdlsGmqKie4TAppVXZUQ==} + '@tiptap/core@3.28.0': + resolution: {integrity: sha512-gUuD5WAYfbDxNSSJya/emh2KSzXZXLUYKW4fEnc1AQ5FE2twzh4LJ9UlKFIawigrUCAksWI5Fy1hRbv+5m4ZdQ==} peerDependencies: - '@tiptap/pm': 3.27.1 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-audio@3.27.1': - resolution: {integrity: sha512-PJ1IAhLTpRUmjmT9nemldD2DiLgUqFUl0U2LRR59/lFcxYyRlhlTbHiibv/Rc2xmYTVpx39lMMfcos3MzuM+3w==} + '@tiptap/extension-audio@3.28.0': + resolution: {integrity: sha512-t2QaBitWf/HcvA1sN0gqeOseoOF05fZtI5Pr+8JUj4HowYVXLQW+yVYaKwGoGnAejQRNuTDJmc4cylVJeKCgJg==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-blockquote@3.27.1': - resolution: {integrity: sha512-VMF7xJx6qEGiX6DTKNiL31NLqypOcd/4sNjFSe8rb41PwejBJh/nOqVIbBvWkiT6NMGFLxMhj7zJ8/zPo1hXeg==} + '@tiptap/extension-blockquote@3.28.0': + resolution: {integrity: sha512-y8Xi6Z3AQLXedz0J8Z3kDsu7SKBmL50gKVXx3RK1Oo1cuCGhNChm3syChkAz3PLAbrPUM5rvJDSZdF2jUrDnng==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-bold@3.27.1': - resolution: {integrity: sha512-TlC5bsS+pqETTrlz4CZz9RO/cKBYtELGIxwtKeivUn3eNfnOxQbbu4WDsiwIfzRFyd0OMnKl6BPM2KnYEehoEQ==} + '@tiptap/extension-bold@3.28.0': + resolution: {integrity: sha512-JhZQmr0AU741bOwjVMfuwJdK4g0TQwwPbeca9aqKHv5zvZw4i4G9G6fESVyMFc3Yag1ffpnq5EtNldHKTnMhlw==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-bubble-menu@3.27.1': - resolution: {integrity: sha512-j/j8Qp9Z5nViade2m7zjrO/CYH/Ca80Qj7aqo0eUaei6FZQ5izlF9o4XQU5EFMAutV6mwynsPUp8FVo5sCuYfw==} + '@tiptap/extension-bubble-menu@3.28.0': + resolution: {integrity: sha512-7AUNoHj2K4XLKCgW4uspeD/ENejPu2BeHvLTsiOoIO+XXDNSi2j0bbaIS8f2s/qnFirscwp/sbznp1qph/76qg==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-bullet-list@3.27.1': - resolution: {integrity: sha512-faCUHnRP47o9Zh9VZZX6EX/569udw9Vopm2PgEKPWuKLE2qaS5WBuUVU0iItdJmKUqaWiOZkpoW4jvnDmj0dfg==} + '@tiptap/extension-bullet-list@3.28.0': + resolution: {integrity: sha512-dSVuNiH7PFMmWGkNER9vfUb5bXUHDARvHbJ3l+APEb+ZiusVN1KFdM3nd/RsW/Rt5Gd3XwlIEU/c2Uf7cxYDcw==} peerDependencies: - '@tiptap/extension-list': 3.27.1 + '@tiptap/extension-list': 3.28.0 - '@tiptap/extension-code-block@3.27.1': - resolution: {integrity: sha512-pHlzmZx2OlHfyQ0yRlT5UL4mGokz947DthZuYefN1OleVqOkHpWBG+2JQwqoNq6bmzMne92zbH32rhcJUEYSjA==} + '@tiptap/extension-code-block@3.28.0': + resolution: {integrity: sha512-bHITDzT0umTLh+SiEVQzIXkCMt/TzbPM1+HQy9ZxgQDHAJj/vdmfNAnP3RCOrz4lETXyhNQ2b6kxeu3lWckxyA==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-code@3.27.1': - resolution: {integrity: sha512-epOUpFfEmBzjvnqvjv2qHX7NAuLo5dlOGV690lWu+sAYMjibuJBeVvAiKPyFCfRCCTUxdbDB3jbaOA1yEcEJ7w==} + '@tiptap/extension-code@3.28.0': + resolution: {integrity: sha512-AUw2Acof3CQE6Q6Y22saK4lyg0nkeJ4XXniU65TdAi/9TE66TGiO4NQJJNNPgu8+VMEwl5j8VsIFK8sfTcNYtg==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-collaboration-caret@3.27.1': - resolution: {integrity: sha512-mKyYMBkgQGWrlZLGKxQvPP28WaTeZOMUfQg0a6KBnexM4vhX7q2xqAGqCsCFaczQaTPsaJ+/6Bela9Ueh0sPdQ==} + '@tiptap/extension-collaboration-caret@3.28.0': + resolution: {integrity: sha512-n8QsBexKjhXCjpreYdOo4XFl/DBKP+3RKzwTNjHv9g8Wic/ir0Eep07joJp2343ZDtK5GDCFNZQu3PA4bHEgCA==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 - '@tiptap/y-tiptap': ^3.0.5 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 + '@tiptap/y-tiptap': ^3.0.7 - '@tiptap/extension-collaboration@3.27.1': - resolution: {integrity: sha512-Da7WeKNIaLsbcHBWlgexMgm5ygoA1mhRroFND1vweLNsWIPxvyjci7jrq/uDN1tSnpqMlJsdyW0tXzbVGYTpMw==} + '@tiptap/extension-collaboration@3.28.0': + resolution: {integrity: sha512-4RUyRPhieqPD7fvCeniIE0QBSwb1exOWJlYvF8s8wWTwc5uywa95gUhRbnHwgTReeG0WW+177eFSEr9xZaUseg==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 - '@tiptap/y-tiptap': ^3.0.5 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 + '@tiptap/y-tiptap': ^3.0.7 yjs: ^13 - '@tiptap/extension-color@3.27.1': - resolution: {integrity: sha512-9R/vW4dDTce0E0HU+AyfSkKhz08Hlrx7noRRj3CKrTwsCTR86wReTKFHlzxhLKZiDrJuV2Y7CtNG7KGrAArf+A==} + '@tiptap/extension-color@3.28.0': + resolution: {integrity: sha512-YbUMUdrgU04GDYoii1wuFVkiTSQoaEQh04kwgQayEQ46aUBEnnaJitWdp39aGMWYTIXD/c9K9cr5Vg8GSKcVxA==} peerDependencies: - '@tiptap/extension-text-style': 3.27.1 + '@tiptap/extension-text-style': 3.28.0 - '@tiptap/extension-document@3.27.1': - resolution: {integrity: sha512-8FbBTkfnRP4iVaoj+2h3iWa+H0eGDD3yTyVCwrmue/sQTkqUNUoSuAZa3GDG4Sd41xdPwTJxl9nUWGgM1qDCnw==} + '@tiptap/extension-document@3.28.0': + resolution: {integrity: sha512-5SAKtlB1Tr/eZFhISL86HqmUup6rItvPtuPyRjmZo/VgbMwXEwiyAh1sMgFp5VNaeSMM14vJSyQpjhSaOmaBqg==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-dropcursor@3.27.1': - resolution: {integrity: sha512-blFf9x9RG0Qr7P3FoAH/033ffa+mMLZn34trVs8Vi0Ppk6FmJAg5HpYFOtmYoeREdNDJ5rHJKV7SoACbOHgskQ==} + '@tiptap/extension-dropcursor@3.28.0': + resolution: {integrity: sha512-JA+dXQjjfJBpD0nVsZeddGVXRsmanESM9+8CtM35hI9wJnYMXay/B+5GUhswO20zhT3zhB2ZOTGe9knu6A8nIQ==} peerDependencies: - '@tiptap/extensions': 3.27.1 + '@tiptap/extensions': 3.28.0 - '@tiptap/extension-floating-menu@3.27.1': - resolution: {integrity: sha512-BmJF1VqB7dSJkgAalrpVFj88WLhxKjcWPuWHOqf2ITrUU2832BhKLXKmxjWUy1gqV8PfNNVWtGfIERy7I0y0+Q==} + '@tiptap/extension-floating-menu@3.28.0': + resolution: {integrity: sha512-59SECvJq3pQfeJBuydEdhQqrpl0oDlk8N1Ovs1Si3/fJa6rEQAJB2zIvcpBHky9Z+5JodI2eueDnv8eimiyGbg==} peerDependencies: '@floating-ui/dom': ^1.0.0 - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-gapcursor@3.27.1': - resolution: {integrity: sha512-QoezN0wdvXIwLQ4ee2ccWDaX3RG0lzgQpIMpMz55oPDhpUVax1+19ApsS53LkcktpS4EbnPL4xO4DaJk0Vp7PQ==} + '@tiptap/extension-gapcursor@3.28.0': + resolution: {integrity: sha512-Wo73kM4q8z4Va9i4+0n1cO0UEMVUVBHPqM6cAqEYXjB4T48LQkKjRsiQydCezm185rQAsmm1dyi72gtvVQfdMg==} peerDependencies: - '@tiptap/extensions': 3.27.1 + '@tiptap/extensions': 3.28.0 - '@tiptap/extension-hard-break@3.27.1': - resolution: {integrity: sha512-iv/m9hzl6jfSj9Q8UEjAxONvCoUDaP7M9SRCPx3PaLNxA230TTD6RE0Ye4zFJ8ze7ZVoJJMAqg9Qpq1iYg2JOQ==} + '@tiptap/extension-hard-break@3.28.0': + resolution: {integrity: sha512-JHIFjX1luJgQ4VFEkIeXZpbc54Qiw+69P8FmlazYTh7AIJ6iLCpMFgQFELnivEAZ+/vS0lMPQubg0rCeM3UrHw==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-heading@3.27.1': - resolution: {integrity: sha512-SrC4l1kEIyv9ZXFaI/8LQqU2MyMmjczw7XXsWUQOTN4YXv0JyVgMNR3cI/wz0d2xsTfBdZ1N85Tdng+Ga1t0Sg==} + '@tiptap/extension-heading@3.28.0': + resolution: {integrity: sha512-rKjGG/Ik2lNaXBNVmONEDm5DBfGDLM1NWgSf5RRfBISrH8Lm1xqFruOB9tIaB0YUoSV3SBOiwTF9svmzvKSoRA==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-highlight@3.27.1': - resolution: {integrity: sha512-IeMIUKbW4oyRkgn92BPYQ0fifkbTwVigKwa107nGDHMokz+pHQPFHy8xG3U7gtUOra/8OUo57bFgNGuP0TaH4A==} + '@tiptap/extension-highlight@3.28.0': + resolution: {integrity: sha512-T8uvQ1aZs+u496XxycMk5zAIwA0DxN+SCdSAFWCU7bqCAen3ulzOsk6RXJ+Jx40GqTV/ShPyIcEuaJVNurXxbg==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-history@3.27.1': - resolution: {integrity: sha512-JKfAT56se4O00Xu7vxGHhQ3YBOkSBiOos1PvRb9/p3lDBILC8AildgntUTXIDknbFhmrLvMLiNhyBmlyLI2IJg==} + '@tiptap/extension-history@3.28.0': + resolution: {integrity: sha512-ScmXXDuBvRdsYW9S6P11+uHvq73fQtCpRhCCK8Vysmqb+IUGIF+RzljEX3haXHZ61wh+XIqOxSedQAyjA2bcXA==} peerDependencies: - '@tiptap/extensions': 3.27.1 + '@tiptap/extensions': 3.28.0 - '@tiptap/extension-horizontal-rule@3.27.1': - resolution: {integrity: sha512-QlKE7qn5qMnIGVGhXQlvYedvLtNJ9z0dmit5w8vPb8tKzW4Spk6M7N2kruprrDA8GBwHfeR5wmF+njfUm34qxg==} + '@tiptap/extension-horizontal-rule@3.28.0': + resolution: {integrity: sha512-neBCMWprkppQUoLWyeJZDHqBw+xKX2oNhLD9MbFlhKIr092zgfP4mpCvQnSkn1OHl156KzZMp070+NyLBjgQ7A==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-image@3.27.1': - resolution: {integrity: sha512-+JTahgQT+NxiGjduaB3qJVyhU/wh4m3pVkht1Earioku2bm/apj5Lb8rSowa/NJYP3B+oQgV/V4YLw5dtDgBoA==} + '@tiptap/extension-image@3.28.0': + resolution: {integrity: sha512-aKGCdEjyujUcQ6tEtq4EPbKxEC16QsKapv3DeZs1c/UHFiEUalbbPHBnxjGnupiLz75IR+07GYqBoZ81kSCxiA==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-italic@3.27.1': - resolution: {integrity: sha512-jGGeyn9uRUnNjSTHpbqhiGsp6KaYTSbV09jDXPJI9cDwfV9hpugLvpaCZd0BMBbhU1B1W6kOfX0BE15qX/HQfA==} + '@tiptap/extension-italic@3.28.0': + resolution: {integrity: sha512-Yur/ELz6dNVKQC7m8wGjtoLFxamGjJmA0rUEEOacTH6J39aFmcSxYkEGNDkYHbZFyHFYqbej6eI3VE0h08O0mg==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-link@3.27.1': - resolution: {integrity: sha512-/2jBfsxBZUDGJmpZifqRQPz7f1E5qpS1BckTZ39TADzUJX+feKy7RJ3DtQ02+8y6SSMzvP9loGVjrk6zEMTk4g==} + '@tiptap/extension-link@3.28.0': + resolution: {integrity: sha512-hy/PqSeTyl317yseFcHGE+3XVfQKQYaL4uZuj27xlrcO7MZ15drt1h9sUZy1FTBF3mr8676pQu7aoEm/Ww4ZRA==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-list-item@3.27.1': - resolution: {integrity: sha512-zwRl01ETfCkWUvtvK5fw9bXtAajMPkvlkE3Cq6JvH3LF7XXJwDtNj5Tj7exacMpCaSZmlNc43vFb2rAYnrnwMA==} + '@tiptap/extension-list-item@3.28.0': + resolution: {integrity: sha512-GbXrnMac6knSetZY33NHwOrgXFPqH28uCklQqmOZQlsrdGqezDKk31qoEMr4GsKW9n/lViAeuc07oIzwLwCMng==} peerDependencies: - '@tiptap/extension-list': 3.27.1 + '@tiptap/extension-list': 3.28.0 - '@tiptap/extension-list-keymap@3.27.1': - resolution: {integrity: sha512-OIMZNlzPSO8WRd4ic73Fxckzl4N1tesjjLL2XApaNA/uMpO0LoF6WSRPAWv+Z24Wp92ARRJAnRP7iZoI5+Jxig==} + '@tiptap/extension-list-keymap@3.28.0': + resolution: {integrity: sha512-u9Wks8c/eQAv0RkULNggOyTKDD69WVbcV9H5cbasPDUbq5XbEFVa9ajxhEGfMmQ5et3EX0QL8qBi50K0GqbIjA==} peerDependencies: - '@tiptap/extension-list': 3.27.1 + '@tiptap/extension-list': 3.28.0 - '@tiptap/extension-list@3.27.1': - resolution: {integrity: sha512-c2Upru7lj0/ZV/Ibww6cNz6sUS8m6Dp/9uygFhYcZOd3X8M0xBIEk42c6m6SQehkPziVA8QOgNJz7sMqsbz1OQ==} + '@tiptap/extension-list@3.28.0': + resolution: {integrity: sha512-zQ66i5DuhVOndmZ0d7H475Y5Yb+BMx+zfCjFkCzEc6WVef5MumtxBVTkGLQ0ibxUaD71s4QQZbjHWagpaTg0eQ==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-ordered-list@3.27.1': - resolution: {integrity: sha512-GYrKqD//9nHJ2r80uXqbDMzRnFpGzbaEQRTSGaO/SH7DvXWFMow8evkOdjQ7PCQO07jNjJo75+A85Jwu3Ov3AA==} + '@tiptap/extension-ordered-list@3.28.0': + resolution: {integrity: sha512-OZNYrKKL9D01ySOujlNbKlLS9/3wGgKNYeoHZUg0e+Ta8WPVvL3W1zH6TgiU5sF2ZSGUBoq3w7mdaO/iROlUkw==} peerDependencies: - '@tiptap/extension-list': 3.27.1 + '@tiptap/extension-list': 3.28.0 - '@tiptap/extension-paragraph@3.27.1': - resolution: {integrity: sha512-7K7eo1gruOgAsnbK+GCV23AUVUI0cL1bTig8HaPneoFMVbig7vddk8jNLKBWO8TXVbG7TuHdnDN4F98vdtwh5Q==} + '@tiptap/extension-paragraph@3.28.0': + resolution: {integrity: sha512-8UMlQIjzqf6r2hSJ/VeJ00RqaOrOB1J5rTk02Vcw2pYeHkOqp+B0ff0HFu4abT9x1q4oXrBAgMsxRtgh/kR14Q==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-placeholder@3.27.1': - resolution: {integrity: sha512-lhcNDcczQ75yJOSywCHb58Hmtg1aPL/2TdYmeLPxVrP048D7rRs133sfONcgyyw0AvhnfmPOkHLTv3QtKSowhw==} + '@tiptap/extension-placeholder@3.28.0': + resolution: {integrity: sha512-zueiqNeCHOshDa8dGGSa/0cOBZkMjPrxQhemjF4tj2yZ6EeWHBPtCE5h/uQCagEePxsoaJtvaCRh88199zrgBw==} peerDependencies: - '@tiptap/extensions': 3.27.1 + '@tiptap/extensions': 3.28.0 - '@tiptap/extension-strike@3.27.1': - resolution: {integrity: sha512-Y3DW1jlSlCNCyMGHP3+3qBNNPS83wuFz4RTYGjZtvRRTCRh7apZme9XRWMq1rN5mJ2Cr7fKocA2/5Bs13KgN6Q==} + '@tiptap/extension-strike@3.28.0': + resolution: {integrity: sha512-VVj2ZZU9QYqiHLcjqMqRYvuHSsokj/AgUl+6TzLrKjlWwyZ18D4H2vkyI0g70iVTKnUpZ3sEy8WA/rqjgBjqBg==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-subscript@3.27.1': - resolution: {integrity: sha512-A8bokr7c7QBndZcMX0F4AK1y4xEYd5MvOczojFusaiTfvAnH3D8PwFbOPQs3ZLB8W9K3bn4g/XH55P1IAPFGyg==} + '@tiptap/extension-subscript@3.28.0': + resolution: {integrity: sha512-lHROuWtzuMv3V4Q11j43Grqci5t0BAMbzIFinPyDSwxDfOCdpj0AwUS+TNNmi4sxZ49QwOXm/vzVMSSi2ChO5g==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-superscript@3.27.1': - resolution: {integrity: sha512-8021uld246dqhtzZdzjvnWcfiJwElPQ50zZe7Fc3QGFiNRDVWQ2ATXGJFmXEopgCU5GuZaytPMtAq4UVDXc6xw==} + '@tiptap/extension-superscript@3.28.0': + resolution: {integrity: sha512-19MWwQIoREjZsEQ7fa/6DUMx15GKZ+iPkOcSdm/PE2rKiDXhec0acWPZTZgH5dluW9GJOMilk7Xl7U8oBNGfjQ==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-table@3.27.1': - resolution: {integrity: sha512-tNB8kjxo0+XPremnWkd6NUikpeJ+JQrss7HntL8GgIxX4t0gkdnLn9yUWb0JzcaYP7Y8iTZZHdkPs0P154DG8Q==} + '@tiptap/extension-table@3.28.0': + resolution: {integrity: sha512-SX2Uwn/bFyrqjbBo2Hg9wfLT/ofByHm4f80BuG0h5/m89Ve78CgtaK2lGz9jHZRM7rKTG6eqel+PUZQUwcSzOg==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-text-align@3.27.1': - resolution: {integrity: sha512-EXawuJBO55wd8WcTbHTMoPhv0CGQxza4yCCPB5Hqz4ZPQwahIr3ej+8yp/kimIl0xokabwZ0/Fu8STQ4AkZv5g==} + '@tiptap/extension-text-align@3.28.0': + resolution: {integrity: sha512-P+KQMTtCpLFq9dRvy/fbyu2BGALulT/1HtsLbpFG4tKATaKa+cFnJ7rLZF7b3AdAMDNtXdnivxAtixAMBt2QWw==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-text-style@3.27.1': - resolution: {integrity: sha512-J48WIl+6YDYTFPhWXUBQk+u7+AKVUqTdvrZOQyPYCGuQMgHrYzgWrI5+HeEifUgXJ5rMIWWP3qytp7KhVVqpDQ==} + '@tiptap/extension-text-style@3.28.0': + resolution: {integrity: sha512-UreMyqZSv770+CXJK/l1qeHUdaNMQAVkxSOPWpiY9FiT8B4OQyjA7WBOlWrzbvVTzfJvXWk2Y3SWgVRx+/Cd9A==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-text@3.27.1': - resolution: {integrity: sha512-6ZwaZwSrDh+KFFv6V1J79oO37yPs7y1bFxvk1/9Ih2rn3Xr5AWz+eMS+n8RpH3djBVVAQpdIAeYQgcn+VCSsTg==} + '@tiptap/extension-text@3.28.0': + resolution: {integrity: sha512-Jqp1LgfY1mnp4TQHoy+vnHyfn6qnnAM6nHgGwbY5zA8b/Xf1Etll6pziTx9p1J1qcfAgSrnjOyAmA++H7VQqww==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-typography@3.27.1': - resolution: {integrity: sha512-hkZyiiv45XQGizdEYxJn86g8yKJxG45kkTztg0m6qjjcdDzjhUAcwVPTZ4ecg3hxwy/XNY7gEBOMGI7iqJl2sw==} + '@tiptap/extension-typography@3.28.0': + resolution: {integrity: sha512-6iwxXqtT6HuFv9LYrpT7YV6TpIauWBXCGGs3liHPrY4P7aMwpRmC3QTbgq655PMpjV8xMT6S2Wa6cQi2CNrXmQ==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-underline@3.27.1': - resolution: {integrity: sha512-N889J4nXN/TPfVt8uF9N1A0SY82E90zwc1y26lqOcw6KWNLmQrlhMh/9OD4ikLDbekmFpOBq/UicpHf/6S8hbQ==} + '@tiptap/extension-underline@3.28.0': + resolution: {integrity: sha512-rwxCS6vTh2DJkNIYQX7JSrrRSmm76e2y48aZeuZO5ShkvLWMuJ3/zqDHRxAQVDiJhuE2Cp8NrTmPYlUc9YrT0A==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extension-unique-id@3.27.1': - resolution: {integrity: sha512-m42wrwKBzxhFMBimlftFSF68EZaQWuxkyT0jjVSZH/HViRtptI9JUk+eUrFz0W6mPQ2R25f6/nHORTNhKGr6tQ==} + '@tiptap/extension-unique-id@3.28.0': + resolution: {integrity: sha512-fVPBul1b0SH9C7T/gehLotKLRBVJgp5PiDQbvcw2iom37Y8cPNV5yKTAGKIHOb1BxRF6IzEnkQKxUAJiJnqv3g==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-youtube@3.27.1': - resolution: {integrity: sha512-3ThjdJ2+oQAq/t1aXDR8fGjRA+L6jJxZoNrOkw/KrysDmoflFd4Vn+7ZctKtop4fkC9GjX8NX6NzkfQoe4lCrQ==} + '@tiptap/extension-youtube@3.28.0': + resolution: {integrity: sha512-87J7YcUmdti7DQjy5CGjRwb+K3ibX36nGlC3XrU/b2ef6snEBCnXwhqtRgofhL10RH/HJ1D1ndnQ5fWkcAQ4MQ==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.28.0 - '@tiptap/extensions@3.27.1': - resolution: {integrity: sha512-1Tdx9faw8k0/83V6X+xCDVhV8yElGt95JxeW3YMkKQJI56QdlPz0xOdJPlMiSGJKinPyVier+x9LJD/YZUZIaw==} + '@tiptap/extensions@3.28.0': + resolution: {integrity: sha512-DJT1khCK+O/pT1gQlAnoKAx6zwDkgv7GtnhSfkqm1/4KHC3x+SSJnBIgBl9oGHymA+DxWbW1EULU4V3d/kT2uQ==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/html@3.27.1': - resolution: {integrity: sha512-DKZv6B/I9JgveIHMMsZDdx2K974HHNIh5o1CeYk1L4xHTWDTa64pXW18IrU8xMFm6RibW3LFCX7f3RTn7Pbq0g==} + '@tiptap/html@3.28.0': + resolution: {integrity: sha512-AnLu6ChKjJt90bjLHi5CqgCVJzjI0ipheDGVUMvJD4QIY0nG8FBchrFx2ARUn2Ktk2qjXdTFQ2YcYuZWB2MmOA==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 happy-dom: ^20.8.9 - '@tiptap/pm@3.27.1': - resolution: {integrity: sha512-Ffjx+vimmBU7zH/KrpXzJid3+pziCe/VL2aexSTP63cyQwKQ65LkFkCKaIsSpFdQQuakVZBGWjCA5RoBV852pw==} + '@tiptap/pm@3.28.0': + resolution: {integrity: sha512-ALcpwZMUdat9gjJKlpscpoqXStoLhU246LPEVBDvJdIsoUKvUu3MrzfXik2Y8mtSGfhjtm9O2TRkWxQiFVMwsQ==} - '@tiptap/react@3.27.1': - resolution: {integrity: sha512-/Wn2fc9zMtX08MXYScDFsm4wJ8lzfhfPEdbtls7WCDlbtrop48PWlkHDBBJrywARfAQTB2mFs9KiFy9yrQm5Lg==} + '@tiptap/react@3.28.0': + resolution: {integrity: sha512-BxzSAqaDEldQ97K/v6+GizU1/Dxx9VWkRh7PLQql6bXlAMiz6T1G3dGt25vvv1hQ1FoMt5ExWY5NkrPmR8G8ew==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tiptap/starter-kit@3.27.1': - resolution: {integrity: sha512-vfxRsqW8rCc0k4pzo0ilU3wobVi2wqVj88VZI2SlgZlNnUAkrDGDIAph7CTa9k9fshV+O1ivpEgPC5yC046jow==} + '@tiptap/starter-kit@3.28.0': + resolution: {integrity: sha512-pqvFpValV+ONtlu3l4s73ROm/tEjoGMmt6uHmSJaLbqTcMHkdWuR3flJ/5brr4IDHe1foxmNicMbRxlje7yBYw==} - '@tiptap/suggestion@3.27.1': - resolution: {integrity: sha512-GNBPRav+lAfXzqmmUAS6ylRAn3G8JfsP6XosjoORxJIQJLx1ktDqwp6tm1Vgz9aGIM2TrBxLS1uBbI1Gb2/1VA==} + '@tiptap/suggestion@3.28.0': + resolution: {integrity: sha512-24k0CvvazBeHAK2+WrV1n0uDBZBHNbDAq1K8b5KkWSxcAU7zSCytmApS1u7clRW5wg2xKNBdDhNhvk7KSm1kIA==} peerDependencies: '@floating-ui/dom': ^1.0.0 - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0 + '@tiptap/pm': 3.28.0 - '@tiptap/y-tiptap@3.0.5': - resolution: {integrity: sha512-WoK5z3jMW+9nzumcWAxEDRCSC7yQmdq4NN0157MaxQBl9dGWwjxJx3+11mb+WAqR37oDeAMKMmSNy+/hm2XGlA==} + '@tiptap/y-tiptap@3.0.7': + resolution: {integrity: sha512-3VG01F7i2JDghWsBZSBKi7ypMiN5UVVSyD18IwoXx7ilm0ZynrTS+XNpmgxfuiSBjdauWk7tUlP04xfM8Bw4Vw==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} peerDependencies: prosemirror-model: ^1.7.1 @@ -5371,9 +5380,6 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - async-lock@1.4.1: - resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} - async-mutex@0.5.0: resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} @@ -5890,6 +5896,14 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crossws@0.4.10: + resolution: {integrity: sha512-pz3oubH/dt12KjqsUB0IuXW4nwRDQ583iDsP4555Cpdqx0NoU7pGlWBcayyFI8f/l/idRpgjMEfwuOxSWJYlIA==} + peerDependencies: + srvx: '>=0.11.5' + peerDependenciesMeta: + srvx: + optional: true + css-select@5.1.0: resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} @@ -8724,20 +8738,20 @@ packages: prosemirror-changeset@2.4.0: resolution: {integrity: sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==} - prosemirror-commands@1.6.2: - resolution: {integrity: sha512-0nDHH++qcf/BuPLYvmqZTUUsPJUCPBUXt0J1ErTcDIS369CTp773itzLGIgIXG4LJXOlwYCr44+Mh4ii6MP1QA==} + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} - prosemirror-dropcursor@1.8.1: - resolution: {integrity: sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==} + prosemirror-dropcursor@1.8.3: + resolution: {integrity: sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==} - prosemirror-gapcursor@1.3.2: - resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==} + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} - prosemirror-history@1.4.1: - resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==} + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} - prosemirror-inputrules@1.4.0: - resolution: {integrity: sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==} + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} prosemirror-keymap@1.2.3: resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} @@ -9675,9 +9689,6 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} - tseep@1.3.1: - resolution: {integrity: sha512-ZPtfk1tQnZVyr7BPtbJ93qaAh2lZuIOpTMjhrYa4XctT8xe7t4SAW9LIxrySDuYMsfNNayE51E/WNGrNVgVicQ==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -12229,41 +12240,41 @@ snapshots: '@floating-ui/utils@0.2.11': {} - '@hocuspocus/common@3.4.4': + '@hocuspocus/common@4.4.0': dependencies: lib0: 0.2.117 - '@hocuspocus/provider@3.4.4(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)': + '@hocuspocus/provider-react@4.4.0(@hocuspocus/provider@4.4.0(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(react@19.2.7)(yjs@13.6.30)': dependencies: - '@hocuspocus/common': 3.4.4 + '@hocuspocus/provider': 4.4.0(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) + react: 19.2.7 + yjs: 13.6.30 + + '@hocuspocus/provider@4.4.0(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)': + dependencies: + '@hocuspocus/common': 4.4.0 '@lifeomic/attempt': 3.0.3 lib0: 0.2.117 - ws: 8.21.0 y-protocols: 1.0.6(yjs@13.6.30) yjs: 13.6.30 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - '@hocuspocus/server@3.4.4(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)': + '@hocuspocus/server@4.4.0(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)': dependencies: - '@hocuspocus/common': 3.4.4 - async-lock: 1.4.1 + '@hocuspocus/common': 4.4.0 async-mutex: 0.5.0 + crossws: 0.4.10 kleur: 4.1.5 lib0: 0.2.117 - ws: 8.21.0 y-protocols: 1.0.6(yjs@13.6.30) yjs: 13.6.30 transitivePeerDependencies: - - bufferutil - - utf-8-validate + - srvx - '@hocuspocus/transformer@3.4.4(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(y-prosemirror@1.3.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)': + '@hocuspocus/transformer@4.4.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(y-prosemirror@1.3.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 - '@tiptap/starter-kit': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 + '@tiptap/starter-kit': 3.28.0 y-prosemirror: 1.3.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) yjs: 13.6.30 @@ -14290,206 +14301,207 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@tiptap/core@3.27.1(@tiptap/pm@3.27.1)': + '@tiptap/core@3.28.0(@tiptap/pm@3.28.0)': dependencies: - '@tiptap/pm': 3.27.1 + '@tiptap/pm': 3.28.0 - '@tiptap/extension-audio@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-audio@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-blockquote@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-blockquote@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 - '@tiptap/extension-bold@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-bold@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-bubble-menu@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-bubble-menu@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: '@floating-ui/dom': 1.7.3 - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 optional: true - '@tiptap/extension-bullet-list@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-bullet-list@3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-list': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-code-block@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-code-block@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 - '@tiptap/extension-code@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-code@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-collaboration-caret@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))': + '@tiptap/extension-collaboration-caret@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(@tiptap/y-tiptap@3.0.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 - '@tiptap/y-tiptap': 3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 + '@tiptap/y-tiptap': 3.0.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) - '@tiptap/extension-collaboration@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)': + '@tiptap/extension-collaboration@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(@tiptap/y-tiptap@3.0.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 - '@tiptap/y-tiptap': 3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 + '@tiptap/y-tiptap': 3.0.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30) yjs: 13.6.30 - '@tiptap/extension-color@3.27.1(@tiptap/extension-text-style@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)))': + '@tiptap/extension-color@3.28.0(@tiptap/extension-text-style@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)))': dependencies: - '@tiptap/extension-text-style': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + '@tiptap/extension-text-style': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-document@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-document@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-dropcursor@3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-dropcursor@3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/extensions': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extensions': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-floating-menu@3.27.1(@floating-ui/dom@1.7.3)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-floating-menu@3.28.0(@floating-ui/dom@1.7.3)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: '@floating-ui/dom': 1.7.3 - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 optional: true - '@tiptap/extension-gapcursor@3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-gapcursor@3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/extensions': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extensions': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-hard-break@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-hard-break@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-heading@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-heading@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-highlight@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-highlight@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-history@3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-history@3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/extensions': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extensions': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-horizontal-rule@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-horizontal-rule@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 - '@tiptap/extension-image@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-image@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-italic@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-italic@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-link@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-link@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 linkifyjs: 4.3.3 - '@tiptap/extension-list-item@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-list-item@3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-list': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-list-keymap@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-list-keymap@3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-list': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 - '@tiptap/extension-ordered-list@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-ordered-list@3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-list': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-paragraph@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-paragraph@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-placeholder@3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-placeholder@3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/extensions': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extensions': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-strike@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-strike@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-subscript@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-subscript@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 - '@tiptap/extension-superscript@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-superscript@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 - '@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-table@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 - '@tiptap/extension-text-align@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-text-align@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-text-style@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-text-style@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-text@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-text@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-typography@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-typography@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-underline@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-underline@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-unique-id@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-unique-id@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 uuid: 14.0.0 - '@tiptap/extension-youtube@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-youtube@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 - '@tiptap/html@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(happy-dom@20.8.9)': + '@tiptap/html@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(happy-dom@20.8.9)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 happy-dom: 20.8.9 - '@tiptap/pm@3.27.1': + '@tiptap/pm@3.28.0': dependencies: prosemirror-changeset: 2.4.0 - prosemirror-commands: 1.6.2 - prosemirror-dropcursor: 1.8.1 - prosemirror-gapcursor: 1.3.2 - prosemirror-history: 1.4.1 - prosemirror-inputrules: 1.4.0 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.3 + prosemirror-gapcursor: 1.4.1 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 prosemirror-keymap: 1.2.3 prosemirror-model: 1.25.9 prosemirror-schema-list: 1.5.1 @@ -14498,10 +14510,10 @@ snapshots: prosemirror-transform: 1.12.0 prosemirror-view: 1.41.9 - '@tiptap/react@3.27.1(@floating-ui/dom@1.7.3)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@tiptap/react@3.28.0(@floating-ui/dom@1.7.3)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) '@types/use-sync-external-store': 0.0.6 @@ -14510,45 +14522,45 @@ snapshots: react-dom: 19.2.7(react@19.2.7) use-sync-external-store: 1.6.0(react@19.2.7) optionalDependencies: - '@tiptap/extension-bubble-menu': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-floating-menu': 3.27.1(@floating-ui/dom@1.7.3)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-bubble-menu': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) + '@tiptap/extension-floating-menu': 3.28.0(@floating-ui/dom@1.7.3)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) transitivePeerDependencies: - '@floating-ui/dom' - '@tiptap/starter-kit@3.27.1': + '@tiptap/starter-kit@3.28.0': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/extension-blockquote': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-bold': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-bullet-list': 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-code': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-code-block': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-document': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-dropcursor': 3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-gapcursor': 3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-hard-break': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-heading': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-horizontal-rule': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-italic': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-link': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-list-item': 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-list-keymap': 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-ordered-list': 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-paragraph': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-strike': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-text': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-underline': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extensions': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/extension-blockquote': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) + '@tiptap/extension-bold': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) + '@tiptap/extension-bullet-list': 3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) + '@tiptap/extension-code': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) + '@tiptap/extension-code-block': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) + '@tiptap/extension-document': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) + '@tiptap/extension-dropcursor': 3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) + '@tiptap/extension-gapcursor': 3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) + '@tiptap/extension-hard-break': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) + '@tiptap/extension-heading': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) + '@tiptap/extension-horizontal-rule': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) + '@tiptap/extension-italic': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) + '@tiptap/extension-link': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) + '@tiptap/extension-list': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) + '@tiptap/extension-list-item': 3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) + '@tiptap/extension-list-keymap': 3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) + '@tiptap/extension-ordered-list': 3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) + '@tiptap/extension-paragraph': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) + '@tiptap/extension-strike': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) + '@tiptap/extension-text': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) + '@tiptap/extension-underline': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) + '@tiptap/extensions': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 - '@tiptap/suggestion@3.27.1(@floating-ui/dom@1.7.3)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/suggestion@3.28.0(@floating-ui/dom@1.7.3)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': dependencies: '@floating-ui/dom': 1.7.3 - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) + '@tiptap/pm': 3.28.0 - '@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)': + '@tiptap/y-tiptap@3.0.7(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.41.9)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)': dependencies: lib0: 0.2.117 prosemirror-model: 1.25.9 @@ -15547,8 +15559,6 @@ snapshots: assertion-error@2.0.1: {} - async-lock@1.4.1: {} - async-mutex@0.5.0: dependencies: tslib: 2.8.1 @@ -16125,6 +16135,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crossws@0.4.10: {} + css-select@5.1.0: dependencies: boolbase: 1.0.0 @@ -19385,33 +19397,33 @@ snapshots: dependencies: prosemirror-transform: 1.12.0 - prosemirror-commands@1.6.2: + prosemirror-commands@1.7.1: dependencies: prosemirror-model: 1.25.9 prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 - prosemirror-dropcursor@1.8.1: + prosemirror-dropcursor@1.8.3: dependencies: prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 prosemirror-view: 1.41.9 - prosemirror-gapcursor@1.3.2: + prosemirror-gapcursor@1.4.1: dependencies: prosemirror-keymap: 1.2.3 prosemirror-model: 1.25.9 prosemirror-state: 1.4.4 prosemirror-view: 1.41.9 - prosemirror-history@1.4.1: + prosemirror-history@1.5.0: dependencies: prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 prosemirror-view: 1.41.9 rope-sequence: 1.3.4 - prosemirror-inputrules@1.4.0: + prosemirror-inputrules@1.5.1: dependencies: prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 @@ -20513,8 +20525,6 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - tseep@1.3.1: {} - tslib@2.8.1: {} tsx@4.21.0: