From 17c539658b8290d8e1135e3e517df205921542e3 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Sat, 11 May 2024 18:16:49 +0530 Subject: [PATCH] feat: pages collaboration --- apiserver/plane/app/views/page/base.py | 18 ++-- packages/editor/core/src/hooks/use-editor.tsx | 7 +- packages/editor/core/src/index.ts | 1 + packages/editor/document-editor/package.json | 7 +- .../src/hooks/use-document-editor.ts | 101 ++++++++++++++++++ .../src/providers/collaboration-provider.ts | 70 ++++++++++++ .../src/ui/extensions/index.tsx | 8 +- .../editor/document-editor/src/ui/index.tsx | 28 +++-- web/components/pages/editor/editor-body.tsx | 85 ++++++++++++--- .../projects/[projectId]/pages/[pageId].tsx | 2 - web/services/api.service.ts | 7 +- web/services/page.service.ts | 20 ++++ yarn.lock | 68 ++++++++---- 13 files changed, 353 insertions(+), 69 deletions(-) create mode 100644 packages/editor/document-editor/src/hooks/use-document-editor.ts create mode 100644 packages/editor/document-editor/src/providers/collaboration-provider.ts diff --git a/apiserver/plane/app/views/page/base.py b/apiserver/plane/app/views/page/base.py index 0f8b6fc98..23a5b8838 100644 --- a/apiserver/plane/app/views/page/base.py +++ b/apiserver/plane/app/views/page/base.py @@ -430,17 +430,17 @@ class PagesDescriptionViewSet(BaseViewSet): if page.description_yjs: Y.apply_update(existing_doc, page.description_yjs) - # Load the new data into a separate YDoc - new_doc = Y.YDoc() - Y.apply_update(new_doc, new_binary_data) + # # Load the new data into a separate YDoc + # new_doc = Y.YDoc() + Y.apply_update(existing_doc, new_binary_data) - # Merge the new data into the existing data - # This will automatically resolve any conflicts - new_state_vector = Y.encode_state_vector(new_doc) - diff = Y.encode_state_as_update(existing_doc, new_state_vector) - Y.apply_update(existing_doc, diff) + # # Merge the new data into the existing data + # # This will automatically resolve any conflicts + # new_state_vector = Y.encode_state_vector(new_doc) + # diff = Y.encode_state_as_update(existing_doc, new_state_vector) + # Y.apply_update(existing_doc, diff) - # Encode the updated state as binary data + # # Encode the updated state as binary data updated_binary_data = Y.encode_state_as_update(existing_doc) # Store the updated binary data diff --git a/packages/editor/core/src/hooks/use-editor.tsx b/packages/editor/core/src/hooks/use-editor.tsx index 778fdc5e4..b87105100 100644 --- a/packages/editor/core/src/hooks/use-editor.tsx +++ b/packages/editor/core/src/hooks/use-editor.tsx @@ -12,18 +12,17 @@ import { insertContentAtSavedSelection } from "src/helpers/insert-content-at-cur import { EditorMenuItemNames, getEditorMenuItems } from "src/ui/menus/menu-items"; import { EditorRefApi } from "src/types/editor-ref-api"; import { IMarking, scrollSummary } from "src/helpers/scroll-to-node"; - -interface CustomEditorProps { +export interface CustomEditorProps { id?: string; uploadFile: UploadImage; restoreFile: RestoreImage; deleteFile: DeleteImage; cancelUploadImage?: () => void; - initialValue: string; + initialValue?: string; editorClassName: string; // undefined when prop is not passed, null if intentionally passed to stop // swr syncing - value: string | null | undefined; + value?: string | null | undefined; onChange?: (json: object, html: string) => void; extensions?: any; editorProps?: EditorProps; diff --git a/packages/editor/core/src/index.ts b/packages/editor/core/src/index.ts index 336daed43..71faac6c9 100644 --- a/packages/editor/core/src/index.ts +++ b/packages/editor/core/src/index.ts @@ -24,6 +24,7 @@ export * from "src/ui/menus/menu-items"; export * from "src/lib/editor-commands"; // types +export type { CustomEditorProps } from "src/hooks/use-editor"; export type { DeleteImage } from "src/types/delete-image"; export type { UploadImage } from "src/types/upload-image"; export type { EditorRefApi, EditorReadOnlyRefApi } from "src/types/editor-ref-api"; diff --git a/packages/editor/document-editor/package.json b/packages/editor/document-editor/package.json index ca2e501e6..4e45f1407 100644 --- a/packages/editor/document-editor/package.json +++ b/packages/editor/document-editor/package.json @@ -34,12 +34,17 @@ "@plane/ui": "*", "@tippyjs/react": "^4.2.6", "@tiptap/core": "^2.1.13", + "@tiptap/extension-collaboration": "^2.3.2", "@tiptap/pm": "^2.1.13", "@tiptap/suggestion": "^2.1.13", "lucide-react": "^0.378.0", "react-popper": "^2.3.0", "tippy.js": "^6.3.7", - "uuid": "^9.0.1" + "uuid": "^9.0.1", + "y-indexeddb": "^9.0.12", + "y-prosemirror": "^1.2.5", + "y-protocols": "^1.0.6", + "yjs": "^13.6.15" }, "devDependencies": { "@types/node": "18.15.3", diff --git a/packages/editor/document-editor/src/hooks/use-document-editor.ts b/packages/editor/document-editor/src/hooks/use-document-editor.ts new file mode 100644 index 000000000..fa82e3c0b --- /dev/null +++ b/packages/editor/document-editor/src/hooks/use-document-editor.ts @@ -0,0 +1,101 @@ +import { useLayoutEffect, useMemo } from "react"; +import { + DeleteImage, + EditorRefApi, + IMentionHighlight, + IMentionSuggestion, + RestoreImage, + UploadImage, + useEditor, +} from "@plane/editor-core"; +import * as Y from "yjs"; +import { CollaborationProvider } from "src/providers/collaboration-provider"; +import { DocumentEditorExtensions } from "src/ui/extensions"; +import { IndexeddbPersistence } from "y-indexeddb"; +import { EditorProps } from "@tiptap/pm/view"; + +type DocumentEditorProps = { + id?: string; + uploadFile: UploadImage; + restoreFile: RestoreImage; + deleteFile: DeleteImage; + cancelUploadImage?: () => void; + value: Uint8Array; + editorClassName: string; + onChange: (binaryString: string, html: string) => void; + extensions?: any; + editorProps?: EditorProps; + forwardedRef?: React.MutableRefObject; + mentionHandler: { + highlights: () => Promise; + suggestions?: () => Promise; + }; + handleEditorReady?: (value: boolean) => void; + placeholder?: string | ((isFocused: boolean, value: string) => string); + setHideDragHandleFunction: (hideDragHandlerFromDragDrop: () => void) => void; + tabIndex?: number; +}; + +export const useDocumentEditor = ({ + uploadFile, + id = "", + deleteFile, + cancelUploadImage, + editorProps = {}, + value, + editorClassName, + onChange, + forwardedRef, + tabIndex, + restoreFile, + handleEditorReady, + mentionHandler, + placeholder, + setHideDragHandleFunction, +}: DocumentEditorProps) => { + const provider = useMemo( + () => + new CollaborationProvider({ + name: id, + onChange, + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [id] + ); + + const yDoc = useMemo(() => { + if (value.byteLength !== 0) Y.applyUpdate(provider.document, value); + return provider.document; + }, [value, provider.document]); + console.log("yDoc", yDoc); + + // indexedDB provider + useLayoutEffect(() => { + const localProvider = new IndexeddbPersistence(id, provider.document); + return () => { + localProvider?.destroy(); + }; + }, [provider, id]); + + const editor = useEditor({ + id, + editorProps, + editorClassName, + restoreFile, + uploadFile, + deleteFile, + cancelUploadImage, + handleEditorReady, + forwardedRef, + mentionHandler, + extensions: DocumentEditorExtensions({ + uploadFile, + setHideDragHandle: setHideDragHandleFunction, + provider, + }), + placeholder, + tabIndex, + }); + + return editor; +}; diff --git a/packages/editor/document-editor/src/providers/collaboration-provider.ts b/packages/editor/document-editor/src/providers/collaboration-provider.ts new file mode 100644 index 000000000..7d9ba20d6 --- /dev/null +++ b/packages/editor/document-editor/src/providers/collaboration-provider.ts @@ -0,0 +1,70 @@ +import * as Y from "yjs"; + +export interface CompleteCollaboratorProviderConfiguration { + /** + * The identifier/name of your document + */ + name: string; + /** + * The actual Y.js document + */ + document: Y.Doc; + /** + * onChange callback + */ + onChange: (binaryString: string, html: string) => void; +} + +export type CollaborationProviderConfiguration = Required> & + Partial; + +export class CollaborationProvider { + public configuration: CompleteCollaboratorProviderConfiguration = { + name: "", + // @ts-expect-error cannot be undefined + document: undefined, + onChange: () => {}, + }; + + intervals: any = { + forceSync: null, + }; + + timeoutId: any; + + constructor(configuration: CollaborationProviderConfiguration) { + this.setConfiguration(configuration); + + this.timeoutId = null; + + this.configuration.document = configuration.document ?? new Y.Doc(); + this.document.on("update", this.documentUpdateHandler.bind(this)); + } + + public setConfiguration(configuration: Partial = {}): void { + this.configuration = { + ...this.configuration, + ...configuration, + }; + } + + get document() { + return this.configuration.document; + } + + documentUpdateHandler(update: Uint8Array, origin: any) { + if (origin === this) return; + + // debounce onChange call + if (this.timeoutId !== null) clearTimeout(this.timeoutId); + + this.timeoutId = setTimeout(() => { + const docAsUint8Array = Y.encodeStateAsUpdate(this.document); + const base64Doc = Buffer.from(docAsUint8Array).toString("base64"); + // const base64Doc = Buffer.from(update).toString("base64"); + + this.configuration.onChange?.(base64Doc, "

"); + this.timeoutId = null; + }, 2000); + } +} diff --git a/packages/editor/document-editor/src/ui/extensions/index.tsx b/packages/editor/document-editor/src/ui/extensions/index.tsx index b2816974e..10c9fa596 100644 --- a/packages/editor/document-editor/src/ui/extensions/index.tsx +++ b/packages/editor/document-editor/src/ui/extensions/index.tsx @@ -2,14 +2,20 @@ import { IssueWidgetPlaceholder } from "src/ui/extensions/widgets/issue-embed-wi import { SlashCommand, DragAndDrop } from "@plane/editor-extensions"; import { UploadImage } from "@plane/editor-core"; +import { CollaborationProvider } from "src/providers/collaboration-provider"; +import Collaboration from "@tiptap/extension-collaboration"; type TArguments = { uploadFile: UploadImage; setHideDragHandle?: (hideDragHandlerFromDragDrop: () => void) => void; + provider: CollaborationProvider; }; -export const DocumentEditorExtensions = ({ uploadFile, setHideDragHandle }: TArguments) => [ +export const DocumentEditorExtensions = ({ uploadFile, setHideDragHandle, provider }: TArguments) => [ SlashCommand(uploadFile), DragAndDrop(setHideDragHandle), IssueWidgetPlaceholder(), + Collaboration.configure({ + document: provider.document, + }), ]; diff --git a/packages/editor/document-editor/src/ui/index.tsx b/packages/editor/document-editor/src/ui/index.tsx index 1f1c5f706..c1f8c2eeb 100644 --- a/packages/editor/document-editor/src/ui/index.tsx +++ b/packages/editor/document-editor/src/ui/index.tsx @@ -4,17 +4,16 @@ import { DeleteImage, RestoreImage, getEditorClassNames, - useEditor, EditorRefApi, IMentionHighlight, IMentionSuggestion, } from "@plane/editor-core"; -import { DocumentEditorExtensions } from "src/ui/extensions"; import { PageRenderer } from "src/ui/components/page-renderer"; +import { useDocumentEditor } from "src/hooks/use-document-editor"; interface IDocumentEditor { - initialValue: string; - value?: string; + id: string; + value: Uint8Array; fileHandler: { cancel: () => void; delete: DeleteImage; @@ -24,7 +23,7 @@ interface IDocumentEditor { handleEditorReady?: (value: boolean) => void; containerClassName?: string; editorClassName?: string; - onChange: (json: object, html: string) => void; + onChange: (binaryString: string, html: string) => void; forwardedRef?: React.MutableRefObject; mentionHandler: { highlights: () => Promise; @@ -37,8 +36,9 @@ interface IDocumentEditor { const DocumentEditor = (props: IDocumentEditor) => { const { onChange, - initialValue, + id, value, + // value, fileHandler, containerClassName, editorClassName = "", @@ -56,26 +56,22 @@ const DocumentEditor = (props: IDocumentEditor) => { const setHideDragHandleFunction = (hideDragHandlerFromDragDrop: () => void) => { setHideDragHandleOnMouseLeave(() => hideDragHandlerFromDragDrop); }; - // use editor - const editor = useEditor({ - onChange(json, html) { - onChange(json, html); - }, + + // use document editor + const editor = useDocumentEditor({ + id, editorClassName, restoreFile: fileHandler.restore, uploadFile: fileHandler.upload, deleteFile: fileHandler.delete, cancelUploadImage: fileHandler.cancel, - initialValue, value, + onChange, handleEditorReady, forwardedRef, mentionHandler, - extensions: DocumentEditorExtensions({ - uploadFile: fileHandler.upload, - setHideDragHandle: setHideDragHandleFunction, - }), placeholder, + setHideDragHandleFunction, tabIndex, }); diff --git a/web/components/pages/editor/editor-body.tsx b/web/components/pages/editor/editor-body.tsx index a896bcc58..8afd46bf5 100644 --- a/web/components/pages/editor/editor-body.tsx +++ b/web/components/pages/editor/editor-body.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useCallback, useEffect, useState } from "react"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; import { Control, Controller } from "react-hook-form"; @@ -22,6 +22,8 @@ import { usePageFilters } from "@/hooks/use-page-filters"; import useReloadConfirmations from "@/hooks/use-reload-confirmation"; // services import { FileService } from "@/services/file.service"; +import { PageService } from "@/services/page.service"; +const pageService = new PageService(); // store import { IPageStore } from "@/store/pages/page.store"; @@ -31,7 +33,6 @@ type Props = { control: Control; editorRef: React.RefObject; readOnlyEditorRef: React.RefObject; - swrPageDetails: TPage | undefined; handleSubmit: () => void; markings: IMarking[]; pageStore: IPageStore; @@ -49,12 +50,13 @@ export const PageEditorBody: React.FC = observer((props) => { editorRef, markings, readOnlyEditorRef, - handleSubmit, + // handleSubmit, pageStore, - swrPageDetails, sidePeekVisible, updateMarkings, } = props; + // states + const [descriptionYJS, setDescriptionYJS] = useState(null); // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -67,6 +69,7 @@ export const PageEditorBody: React.FC = observer((props) => { } = useMember(); // derived values const workspaceId = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString())?.id ?? "" : ""; + const pageId = pageStore?.id ?? ""; const pageTitle = pageStore?.name ?? ""; const pageDescription = pageStore?.description_html; const { description_html, isContentEditable, updateTitle, isSubmitting, setIsSubmitting } = pageStore; @@ -82,13 +85,70 @@ export const PageEditorBody: React.FC = observer((props) => { // page filters const { isFullWidth } = usePageFilters(); - const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting"); + useReloadConfirmations(isSubmitting === "submitting"); + + // const { data: pageDescriptionYJS } = useSWR( + // workspaceSlug && projectId && pageId ? `PAGE_DESCRIPTION_${workspaceSlug}_${projectId}_${pageId}` : null, + // workspaceSlug && projectId && pageId + // ? () => pageService.fetchDescriptionYJS(workspaceSlug.toString(), projectId.toString(), pageId.toString()) + // : null + // ); + + const handleDescriptionChange = useCallback( + (binaryString: string, descriptionHTML: string) => { + if (!workspaceSlug || !projectId || !pageId) return; + pageService.updateDescriptionYJS(workspaceSlug.toString(), projectId.toString(), pageId.toString(), { + description_yjs: binaryString, + description_html: descriptionHTML, + }); + // setIsSubmitting("submitting"); + // setShowAlert(true); + // onChange(description_html); + // handleSubmit(); + }, + [pageId, projectId, workspaceSlug] + ); + + useEffect(() => { + const fetchDescription = async () => { + if (!workspaceSlug || !projectId || !pageId) return; + console.log("fetching..."); + + const response = await fetch( + `http://localhost:8000/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/description/`, + { + credentials: "include", + method: "GET", + headers: { + "Content-Type": "application/octet-stream", + }, + } + ); + const data = await response.arrayBuffer(); + setDescriptionYJS(new Uint8Array(data)); + // __AUTO_GENERATED_PRINT_VAR_START__ + console.log("fetchById data: %s", data); // __AUTO_GENERATED_PRINT_VAR_END__ + // if (data.byteLength === 0) { + // const yjs = await fetchByIdIfExists(workspaceSlug, projectId, pageId); + // if (yjs) { + // console.log("not found in db:", yjs, yjs instanceof Uint8Array); + // return yjs; + // } + // } + }; + + const interval = setInterval(() => { + fetchDescription(); + }, 15000); + + return () => clearInterval(interval); + }, [pageId, projectId, workspaceSlug]); useEffect(() => { updateMarkings(description_html ?? "

"); }, [description_html, updateMarkings]); - if (pageDescription === undefined) return ; + if (pageDescription === undefined || pageId === undefined || !descriptionYJS) return ; return (
@@ -125,8 +185,9 @@ export const PageEditorBody: React.FC = observer((props) => { ( + render={() => ( = observer((props) => { upload: fileService.getUploadFileFunction(workspaceSlug as string, setIsSubmitting), }} handleEditorReady={handleEditorReady} - initialValue={pageDescription ?? "

"} - value={swrPageDetails?.description_html ?? "

"} + value={descriptionYJS} ref={editorRef} containerClassName="p-0 pb-64" editorClassName="lg:px-10 pl-8" - onChange={(_description_json, description_html) => { - setIsSubmitting("submitting"); - setShowAlert(true); - onChange(description_html); - handleSubmit(); - }} + onChange={handleDescriptionChange} mentionHandler={{ highlights: mentionHighlights, suggestions: mentionSuggestions, diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index 524384ec8..00815715b 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -50,7 +50,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { // fetching page details const { - data: swrPageDetails, isValidating, error: pageDetailsError, } = useSWR(pageId ? `PAGE_DETAILS_${pageId}` : null, pageId ? () => getPageById(pageId.toString()) : null, { @@ -145,7 +144,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { /> )} setEditorReady(val)} diff --git a/web/services/api.service.ts b/web/services/api.service.ts index d5e8f8951..e191cd8f7 100644 --- a/web/services/api.service.ts +++ b/web/services/api.service.ts @@ -31,8 +31,11 @@ export abstract class APIService { ); } - get(url: string, params = {}) { - return this.axiosInstance.get(url, params); + get(url: string, params = {}, config = {}) { + return this.axiosInstance.get(url, { + ...params, + ...config, + }); } post(url: string, data = {}, config = {}) { diff --git a/web/services/page.service.ts b/web/services/page.service.ts index f84f54754..00a39f9da 100644 --- a/web/services/page.service.ts +++ b/web/services/page.service.ts @@ -119,4 +119,24 @@ export class PageService extends APIService { throw error?.response?.data; }); } + + async fetchDescriptionYJS(workspaceSlug: string, projectId: string, pageId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/description/`, { + headers: { + "Content-Type": "application/octet-stream", + }, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async updateDescriptionYJS(workspaceSlug: string, projectId: string, pageId: string, data: any): Promise { + return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/description/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } } diff --git a/yarn.lock b/yarn.lock index c25eddcda..eadb12c30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2410,6 +2410,11 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.3.1.tgz#cea919becab684688819b29481a5c43ee1ee9c52" integrity sha512-bVX0EnDZoRXnoA7dyoZe7w2gdRjxmFEcsatHLkcr3R3x4k9oSgZXLe1C2jGbjJWr4j32tYXZ1cpKte6f1WUKzg== +"@tiptap/extension-collaboration@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@tiptap/extension-collaboration/-/extension-collaboration-2.3.2.tgz#0780eabbe2e72665ed83f86dc70790589d1d0ff1" + integrity sha512-1vN+crj5KgqoJhDV+CrfIrBWDIjfpVxiEWHBk+yQU/G2vmyQfbN/R/5gH6rOw5GT3mHqgWFtCDJo4+H/2Ete4w== + "@tiptap/extension-document@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.3.1.tgz#c2c3a1d1f87e262872012508555eda8227a3bc7a" @@ -2757,7 +2762,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@18.2.48", "@types/react@^18.2.42", "@types/react@^18.2.48": +"@types/react@*", "@types/react@^18.2.42", "@types/react@^18.2.48": version "18.2.48" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1" integrity sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w== @@ -5635,6 +5640,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isomorphic.js@^0.2.4: + version "0.2.5" + resolved "https://registry.yarnpkg.com/isomorphic.js/-/isomorphic.js-0.2.5.tgz#13eecf36f2dba53e85d355e11bf9d4208c6f7f88" + integrity sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw== + iterator.prototype@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" @@ -5845,6 +5855,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lib0@^0.2.42, lib0@^0.2.74, lib0@^0.2.85, lib0@^0.2.86: + version "0.2.93" + resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.93.tgz#95487c2a97657313cb1d91fbcf9f6d64b7fcd062" + integrity sha512-M5IKsiFJYulS+8Eal8f+zAqf5ckm1vffW0fFDxfgxJ+uiVopvDdd3PxJmz0GsVi3YNO7QCFSq0nAsiDmNhLj9Q== + dependencies: + isomorphic.js "^0.2.4" + lie@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" @@ -7954,16 +7971,8 @@ streamx@^2.15.0, streamx@^2.16.1: optionalDependencies: bare-events "^2.2.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: + name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8043,14 +8052,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9137,6 +9139,27 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +y-indexeddb@^9.0.12: + version "9.0.12" + resolved "https://registry.yarnpkg.com/y-indexeddb/-/y-indexeddb-9.0.12.tgz#73657f31d52886d7532256610babf5cca4ad5e58" + integrity sha512-9oCFRSPPzBK7/w5vOkJBaVCQZKHXB/v6SIT+WYhnJxlEC61juqG0hBrAf+y3gmSMLFLwICNH9nQ53uscuse6Hg== + dependencies: + lib0 "^0.2.74" + +y-prosemirror@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/y-prosemirror/-/y-prosemirror-1.2.5.tgz#c448f80a6017190bc69a30a33f3930e9924fad3a" + integrity sha512-T/JATxC8P2Dbvq/dAiaiztD1a8KEwRP8oLRlT8YlaZdNlLGE1Ea0IJ8If25UlDYmk+4+uqLbqT/S+dzUmwwgbA== + dependencies: + lib0 "^0.2.42" + +y-protocols@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/y-protocols/-/y-protocols-1.0.6.tgz#66dad8a95752623443e8e28c0e923682d2c0d495" + integrity sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q== + dependencies: + lib0 "^0.2.85" + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" @@ -9152,6 +9175,13 @@ yaml@^2.3.4: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.2.tgz#7a2b30f2243a5fc299e1f14ca58d475ed4bc5362" integrity sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA== +yjs@^13.6.15: + version "13.6.15" + resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.6.15.tgz#5a2402632aabf83e5baf56342b4c82fe40859306" + integrity sha512-moFv4uNYhp8BFxIk3AkpoAnnjts7gwdpiG8RtyFiKbMtxKCS0zVZ5wPaaGpwC3V2N/K8TK8MwtSI3+WO9CHWjQ== + dependencies: + lib0 "^0.2.86" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"