[WEB-1435] dev: conflict free issue descriptions (#5912)

* chore: new description binary endpoints

* chore: conflict free issue description

* chore: fix submitting status

* chore: update yjs utils

* chore: handle component re-mounting

* chore: update buffer response type

* chore: add try catch for issue description update

* chore: update buffer response type

* chore: description binary in retrieve

* chore: update issue description hook

* chore: decode description binary

* chore: migrations fixes and cleanup

* chore: migration fixes

* fix: inbox issue description

* chore: move update operations to the issue store

* fix: merge conflicts

* chore: reverted the commit

* chore: removed the unwanted imports

* chore: remove unnecessary props

* chore: remove unused services

* chore: update live server error handling

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
Aaryan Khandelwal 2024-11-15 16:38:58 +05:30 committed by GitHub
parent 229610513a
commit e9680cab74
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 1466 additions and 358 deletions

View file

@ -8,7 +8,7 @@ import { IssueWidget } from "@/extensions";
// helpers
import { getEditorClassNames } from "@/helpers/common";
// hooks
import { useCollaborativeEditor } from "@/hooks/use-collaborative-editor";
import { useCollaborativeDocumentEditor } from "@/hooks/use-collaborative-document-editor";
// types
import { EditorRefApi, ICollaborativeDocumentEditor } from "@/types";
@ -43,7 +43,7 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => {
}
// use document editor
const { editor, hasServerConnectionFailed, hasServerSynced } = useCollaborativeEditor({
const { editor, hasServerConnectionFailed, hasServerSynced } = useCollaborativeDocumentEditor({
onTransaction,
disabledExtensions,
editorClassName,

View file

@ -8,7 +8,7 @@ import { IssueWidget } from "@/extensions";
// helpers
import { getEditorClassNames } from "@/helpers/common";
// hooks
import { useReadOnlyCollaborativeEditor } from "@/hooks/use-read-only-collaborative-editor";
import { useCollaborativeDocumentReadOnlyEditor } from "@/hooks/use-collaborative-document-read-only-editor";
// types
import { EditorReadOnlyRefApi, ICollaborativeDocumentReadOnlyEditor } from "@/types";
@ -36,7 +36,7 @@ const CollaborativeDocumentReadOnlyEditor = (props: ICollaborativeDocumentReadOn
);
}
const { editor, hasServerConnectionFailed, hasServerSynced } = useReadOnlyCollaborativeEditor({
const { editor, hasServerConnectionFailed, hasServerSynced } = useCollaborativeDocumentReadOnlyEditor({
editorClassName,
extensions,
fileHandler,

View file

@ -1,4 +1,4 @@
import { Editor, Extension } from "@tiptap/core";
import { AnyExtension, Editor } from "@tiptap/core";
// components
import { EditorContainer } from "@/components/editors";
// constants
@ -12,7 +12,7 @@ import { EditorContentWrapper } from "./editor-content";
type Props = IEditorProps & {
children?: (editor: Editor) => React.ReactNode;
extensions: Extension<any, any>[];
extensions: AnyExtension[];
};
export const EditorWrapper: React.FC<Props> = (props) => {

View file

@ -0,0 +1,72 @@
import React from "react";
// components
import { EditorContainer, EditorContentWrapper } from "@/components/editors";
import { EditorBubbleMenu } from "@/components/menus";
// constants
import { DEFAULT_DISPLAY_CONFIG } from "@/constants/config";
// helpers
import { getEditorClassNames } from "@/helpers/common";
// hooks
import { useCollaborativeRichTextEditor } from "@/hooks/use-collaborative-rich-text-editor";
// types
import { EditorRefApi, ICollaborativeRichTextEditor } from "@/types";
const CollaborativeRichTextEditor = (props: ICollaborativeRichTextEditor) => {
const {
containerClassName,
displayConfig = DEFAULT_DISPLAY_CONFIG,
editorClassName,
fileHandler,
forwardedRef,
id,
mentionHandler,
onChange,
placeholder,
tabIndex,
value,
} = props;
const { editor } = useCollaborativeRichTextEditor({
editorClassName,
fileHandler,
forwardedRef,
id,
mentionHandler,
onChange,
placeholder,
tabIndex,
value,
});
const editorContainerClassName = getEditorClassNames({
noBorder: true,
borderOnFocus: false,
containerClassName,
});
if (!editor) return null;
return (
<EditorContainer
displayConfig={displayConfig}
editor={editor}
editorContainerClassName={editorContainerClassName}
id={id}
>
<EditorBubbleMenu editor={editor} />
<div className="flex flex-col">
<EditorContentWrapper editor={editor} id={id} tabIndex={tabIndex} />
</div>
</EditorContainer>
);
};
const CollaborativeRichTextEditorWithRef = React.forwardRef<EditorRefApi, ICollaborativeRichTextEditor>(
(props, ref) => (
<CollaborativeRichTextEditor {...props} forwardedRef={ref as React.MutableRefObject<EditorRefApi | null>} />
)
);
CollaborativeRichTextEditorWithRef.displayName = "CollaborativeRichTextEditorWithRef";
export { CollaborativeRichTextEditorWithRef };

View file

@ -0,0 +1,70 @@
import React from "react";
// components
import { EditorContainer, EditorContentWrapper } from "@/components/editors";
import { EditorBubbleMenu } from "@/components/menus";
// constants
import { DEFAULT_DISPLAY_CONFIG } from "@/constants/config";
// helpers
import { getEditorClassNames } from "@/helpers/common";
// hooks
import { useCollaborativeRichTextReadOnlyEditor } from "@/hooks/use-collaborative-rich-text-read-only-editor";
// types
import { EditorReadOnlyRefApi, ICollaborativeRichTextReadOnlyEditor } from "@/types";
const CollaborativeRichTextReadOnlyEditor = (props: ICollaborativeRichTextReadOnlyEditor) => {
const {
containerClassName,
displayConfig = DEFAULT_DISPLAY_CONFIG,
editorClassName,
fileHandler,
forwardedRef,
id,
mentionHandler,
value,
} = props;
const { editor } = useCollaborativeRichTextReadOnlyEditor({
editorClassName,
fileHandler,
forwardedRef,
id,
mentionHandler,
value,
});
const editorContainerClassName = getEditorClassNames({
noBorder: true,
borderOnFocus: false,
containerClassName,
});
if (!editor) return null;
return (
<EditorContainer
displayConfig={displayConfig}
editor={editor}
editorContainerClassName={editorContainerClassName}
id={id}
>
<EditorBubbleMenu editor={editor} />
<div className="flex flex-col">
<EditorContentWrapper editor={editor} id={id} />
</div>
</EditorContainer>
);
};
const CollaborativeRichTextReadOnlyEditorWithRef = React.forwardRef<
EditorReadOnlyRefApi,
ICollaborativeRichTextReadOnlyEditor
>((props, ref) => (
<CollaborativeRichTextReadOnlyEditor
{...props}
forwardedRef={ref as React.MutableRefObject<EditorReadOnlyRefApi | null>}
/>
));
CollaborativeRichTextReadOnlyEditorWithRef.displayName = "CollaborativeRichTextReadOnlyEditorWithRef";
export { CollaborativeRichTextReadOnlyEditorWithRef };

View file

@ -1,2 +1,4 @@
export * from "./collaborative-editor";
export * from "./collaborative-read-only-editor";
export * from "./editor";
export * from "./read-only-editor";

View file

@ -0,0 +1,132 @@
import { CoreEditorExtensionsWithoutProps, DocumentEditorExtensionsWithoutProps } from "@/extensions";
import { getSchema } from "@tiptap/core";
import { generateHTML, generateJSON } from "@tiptap/html";
import { prosemirrorJSONToYDoc, yXmlFragmentToProseMirrorRootNode } from "y-prosemirror";
import * as Y from "yjs";
// editor extension configs
const RICH_TEXT_EDITOR_EXTENSIONS = CoreEditorExtensionsWithoutProps;
const DOCUMENT_EDITOR_EXTENSIONS = [...CoreEditorExtensionsWithoutProps, ...DocumentEditorExtensionsWithoutProps];
// editor schemas
const richTextEditorSchema = getSchema(RICH_TEXT_EDITOR_EXTENSIONS);
const documentEditorSchema = getSchema(DOCUMENT_EDITOR_EXTENSIONS);
/**
* @description apply updates to a doc and return the updated doc in binary format
* @param {Uint8Array} document
* @param {Uint8Array} updates
* @returns {Uint8Array}
*/
export const applyUpdates = (document: Uint8Array, updates?: Uint8Array): Uint8Array => {
const yDoc = new Y.Doc();
Y.applyUpdate(yDoc, document);
if (updates) {
Y.applyUpdate(yDoc, updates);
}
const encodedDoc = Y.encodeStateAsUpdate(yDoc);
return encodedDoc;
};
/**
* @description this function encodes binary data to base64 string
* @param {Uint8Array} document
* @returns {string}
*/
export const convertBinaryDataToBase64String = (document: Uint8Array): string =>
Buffer.from(document).toString("base64");
/**
* @description this function decodes base64 string to binary data
* @param {string} document
* @returns {ArrayBuffer}
*/
export const convertBase64StringToBinaryData = (document: string): ArrayBuffer => Buffer.from(document, "base64");
/**
* @description this function generates the binary equivalent of html content for the rich text editor
* @param {string} descriptionHTML
* @returns {Uint8Array}
*/
export const getBinaryDataFromRichTextEditorHTMLString = (descriptionHTML: string): Uint8Array => {
// convert HTML to JSON
const contentJSON = generateJSON(descriptionHTML ?? "<p></p>", RICH_TEXT_EDITOR_EXTENSIONS);
// convert JSON to Y.Doc format
const transformedData = prosemirrorJSONToYDoc(richTextEditorSchema, contentJSON, "default");
// convert Y.Doc to Uint8Array format
const encodedData = Y.encodeStateAsUpdate(transformedData);
return encodedData;
};
/**
* @description this function generates the binary equivalent of html content for the document editor
* @param {string} descriptionHTML
* @returns {Uint8Array}
*/
export const getBinaryDataFromDocumentEditorHTMLString = (descriptionHTML: string): Uint8Array => {
// convert HTML to JSON
const contentJSON = generateJSON(descriptionHTML ?? "<p></p>", DOCUMENT_EDITOR_EXTENSIONS);
// convert JSON to Y.Doc format
const transformedData = prosemirrorJSONToYDoc(documentEditorSchema, contentJSON, "default");
// convert Y.Doc to Uint8Array format
const encodedData = Y.encodeStateAsUpdate(transformedData);
return encodedData;
};
/**
* @description this function generates all document formats for the provided binary data for the rich text editor
* @param {Uint8Array} description
* @returns
*/
export const getAllDocumentFormatsFromRichTextEditorBinaryData = (
description: Uint8Array
): {
contentBinaryEncoded: string;
contentJSON: object;
contentHTML: string;
} => {
// encode binary description data
const base64Data = convertBinaryDataToBase64String(description);
const yDoc = new Y.Doc();
Y.applyUpdate(yDoc, description);
// convert to JSON
const type = yDoc.getXmlFragment("default");
const contentJSON = yXmlFragmentToProseMirrorRootNode(type, richTextEditorSchema).toJSON();
// convert to HTML
const contentHTML = generateHTML(contentJSON, RICH_TEXT_EDITOR_EXTENSIONS);
return {
contentBinaryEncoded: base64Data,
contentJSON,
contentHTML,
};
};
/**
* @description this function generates all document formats for the provided binary data for the document editor
* @param {Uint8Array} description
* @returns
*/
export const getAllDocumentFormatsFromDocumentEditorBinaryData = (
description: Uint8Array
): {
contentBinaryEncoded: string;
contentJSON: object;
contentHTML: string;
} => {
// encode binary description data
const base64Data = convertBinaryDataToBase64String(description);
const yDoc = new Y.Doc();
Y.applyUpdate(yDoc, description);
// convert to JSON
const type = yDoc.getXmlFragment("default");
const contentJSON = yXmlFragmentToProseMirrorRootNode(type, documentEditorSchema).toJSON();
// convert to HTML
const contentHTML = generateHTML(contentJSON, DOCUMENT_EDITOR_EXTENSIONS);
return {
contentBinaryEncoded: base64Data,
contentJSON,
contentHTML,
};
};

View file

@ -1,16 +0,0 @@
import * as Y from "yjs";
/**
* @description apply updates to a doc and return the updated doc in base64(binary) format
* @param {Uint8Array} document
* @param {Uint8Array} updates
* @returns {string} base64(binary) form of the updated doc
*/
export const applyUpdates = (document: Uint8Array, updates: Uint8Array): Uint8Array => {
const yDoc = new Y.Doc();
Y.applyUpdate(yDoc, document);
Y.applyUpdate(yDoc, updates);
const encodedDoc = Y.encodeStateAsUpdate(yDoc);
return encodedDoc;
};

View file

@ -9,9 +9,9 @@ import { useEditor } from "@/hooks/use-editor";
// plane editor extensions
import { DocumentEditorAdditionalExtensions } from "@/plane-editor/extensions";
// types
import { TCollaborativeEditorProps } from "@/types";
import { TCollaborativeDocumentEditorHookProps } from "@/types";
export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
export const useCollaborativeDocumentEditor = (props: TCollaborativeDocumentEditorHookProps) => {
const {
onTransaction,
disabledExtensions,
@ -102,7 +102,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
forwardedRef,
mentionHandler,
placeholder,
provider,
providerDocument: provider.document,
tabIndex,
});

View file

@ -7,9 +7,9 @@ import { HeadingListExtension } from "@/extensions";
// hooks
import { useReadOnlyEditor } from "@/hooks/use-read-only-editor";
// types
import { TReadOnlyCollaborativeEditorProps } from "@/types";
import { TCollaborativeDocumentReadOnlyEditorHookProps } from "@/types";
export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEditorProps) => {
export const useCollaborativeDocumentReadOnlyEditor = (props: TCollaborativeDocumentReadOnlyEditorHookProps) => {
const {
editorClassName,
editorProps = {},
@ -79,7 +79,7 @@ export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEdit
forwardedRef,
handleEditorReady,
mentionHandler,
provider,
providerDocument: provider.document,
});
return {

View file

@ -0,0 +1,78 @@
import { useEffect, useMemo } from "react";
import Collaboration from "@tiptap/extension-collaboration";
import * as Y from "yjs";
// extensions
import { HeadingListExtension, SideMenuExtension } from "@/extensions";
// hooks
import { useEditor } from "@/hooks/use-editor";
// providers
import { CustomCollaborationProvider } from "@/providers";
// types
import { TCollaborativeRichTextEditorHookProps } from "@/types";
export const useCollaborativeRichTextEditor = (props: TCollaborativeRichTextEditorHookProps) => {
const {
editorClassName,
editorProps = {},
extensions,
fileHandler,
forwardedRef,
handleEditorReady,
id,
mentionHandler,
onChange,
placeholder,
tabIndex,
value,
} = props;
// initialize custom collaboration provider
const provider = useMemo(
() =>
new CustomCollaborationProvider({
name: id,
onChange,
}),
[id]
);
useEffect(() => {
if (provider.hasSynced) return;
if (value && value.length > 0) {
try {
Y.applyUpdate(provider.document, value);
provider.hasSynced = true;
} catch (error) {
console.error("Error applying binary updates to the description", error);
}
}
}, [value, provider.document]);
const editor = useEditor({
id,
editorProps,
editorClassName,
enableHistory: false,
extensions: [
SideMenuExtension({
aiEnabled: false,
dragDropEnabled: true,
}),
HeadingListExtension,
Collaboration.configure({
document: provider.document,
}),
...(extensions ?? []),
],
fileHandler,
handleEditorReady,
forwardedRef,
mentionHandler,
placeholder,
providerDocument: provider.document,
tabIndex,
});
return {
editor,
};
};

View file

@ -0,0 +1,64 @@
import { useEffect, useMemo } from "react";
import Collaboration from "@tiptap/extension-collaboration";
import * as Y from "yjs";
// extensions
import { HeadingListExtension, SideMenuExtension } from "@/extensions";
// hooks
import { useReadOnlyEditor } from "@/hooks/use-read-only-editor";
// providers
import { CustomCollaborationProvider } from "@/providers";
// types
import { TCollaborativeRichTextReadOnlyEditorHookProps } from "@/types";
export const useCollaborativeRichTextReadOnlyEditor = (props: TCollaborativeRichTextReadOnlyEditorHookProps) => {
const {
editorClassName,
editorProps = {},
extensions,
fileHandler,
forwardedRef,
handleEditorReady,
id,
mentionHandler,
value,
} = props;
// initialize custom collaboration provider
const provider = useMemo(
() =>
new CustomCollaborationProvider({
name: id,
}),
[id]
);
useEffect(() => {
if (value.length > 0) {
Y.applyUpdate(provider.document, value);
}
}, [value, provider.document]);
const editor = useReadOnlyEditor({
editorProps,
editorClassName,
extensions: [
SideMenuExtension({
aiEnabled: false,
dragDropEnabled: true,
}),
HeadingListExtension,
Collaboration.configure({
document: provider.document,
}),
...(extensions ?? []),
],
fileHandler,
handleEditorReady,
forwardedRef,
mentionHandler,
providerDocument: provider.document,
});
return {
editor,
};
};

View file

@ -1,5 +1,4 @@
import { useImperativeHandle, useRef, MutableRefObject, useState, useEffect } from "react";
import { HocuspocusProvider } from "@hocuspocus/provider";
import { DOMSerializer } from "@tiptap/pm/model";
import { Selection } from "@tiptap/pm/state";
import { EditorProps } from "@tiptap/pm/view";
@ -36,7 +35,7 @@ export interface CustomEditorProps {
onTransaction?: () => void;
autofocus?: boolean;
placeholder?: string | ((isFocused: boolean, value: string) => string);
provider?: HocuspocusProvider;
providerDocument?: Y.Doc;
tabIndex?: number;
// undefined when prop is not passed, null if intentionally passed to stop
// swr syncing
@ -58,7 +57,7 @@ export const useEditor = (props: CustomEditorProps) => {
onChange,
onTransaction,
placeholder,
provider,
providerDocument,
tabIndex,
value,
autofocus = false,
@ -206,7 +205,7 @@ export const useEditor = (props: CustomEditorProps) => {
return markdownOutput;
},
getDocument: () => {
const documentBinary = provider?.document ? Y.encodeStateAsUpdate(provider?.document) : null;
const documentBinary = providerDocument ? Y.encodeStateAsUpdate(providerDocument) : null;
const documentHTML = editorRef.current?.getHTML() ?? "<p></p>";
const documentJSON = editorRef.current?.getJSON() ?? null;
@ -284,7 +283,7 @@ export const useEditor = (props: CustomEditorProps) => {
words: editorRef?.current?.storage?.characterCount?.words?.() ?? 0,
}),
setProviderDocument: (value) => {
const document = provider?.document;
const document = providerDocument;
if (!document) return;
Y.applyUpdate(document, value);
},

View file

@ -1,5 +1,4 @@
import { useImperativeHandle, useRef, MutableRefObject, useEffect } from "react";
import { HocuspocusProvider } from "@hocuspocus/provider";
import { EditorProps } from "@tiptap/pm/view";
import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
import * as Y from "yjs";
@ -24,7 +23,7 @@ interface CustomReadOnlyEditorProps {
mentionHandler: {
highlights: () => Promise<IMentionHighlight[]>;
};
provider?: HocuspocusProvider;
providerDocument?: Y.Doc;
}
export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
@ -37,7 +36,7 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
fileHandler,
handleEditorReady,
mentionHandler,
provider,
providerDocument,
} = props;
const editor = useCustomEditor({
@ -86,7 +85,7 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
return markdownOutput;
},
getDocument: () => {
const documentBinary = provider?.document ? Y.encodeStateAsUpdate(provider?.document) : null;
const documentBinary = providerDocument ? Y.encodeStateAsUpdate(providerDocument) : null;
const documentHTML = editorRef.current?.getHTML() ?? "<p></p>";
const documentJSON = editorRef.current?.getJSON() ?? null;

View file

@ -0,0 +1,61 @@
import * as Y from "yjs";
export interface CompleteCollaborationProviderConfiguration {
/**
* The identifier/name of your document
*/
name: string;
/**
* The actual Y.js document
*/
document: Y.Doc;
/**
* onChange callback
*/
onChange: (updates: Uint8Array) => void;
}
export type CollaborationProviderConfiguration = Required<Pick<CompleteCollaborationProviderConfiguration, "name">> &
Partial<CompleteCollaborationProviderConfiguration>;
export class CustomCollaborationProvider {
public hasSynced: boolean;
public configuration: CompleteCollaborationProviderConfiguration = {
name: "",
document: new Y.Doc(),
onChange: () => {},
};
constructor(configuration: CollaborationProviderConfiguration) {
this.hasSynced = false;
this.setConfiguration(configuration);
this.document.on("update", this.documentUpdateHandler.bind(this));
this.document.on("destroy", this.documentDestroyHandler.bind(this));
}
public setConfiguration(configuration: Partial<CompleteCollaborationProviderConfiguration> = {}): void {
this.configuration = {
...this.configuration,
...configuration,
};
}
get document() {
return this.configuration.document;
}
async documentUpdateHandler(_update: Uint8Array, origin: any) {
if (!this.hasSynced) return;
// return if the update is from the provider itself
if (origin === this) return;
// call onChange with the update
const stateVector = Y.encodeStateAsUpdate(this.document);
this.configuration.onChange?.(stateVector);
}
documentDestroyHandler() {
this.document.off("update", this.documentUpdateHandler);
this.document.off("destroy", this.documentDestroyHandler);
}
}

View file

@ -0,0 +1 @@
export * from "./custom-collaboration-provider";

View file

@ -19,7 +19,7 @@ export type TServerHandler = {
onServerError?: () => void;
};
type TCollaborativeEditorHookProps = {
type TCollaborativeEditorHookCommonProps = {
disabledExtensions?: TExtensions[];
editorClassName: string;
editorProps?: EditorProps;
@ -30,12 +30,9 @@ type TCollaborativeEditorHookProps = {
highlights: () => Promise<IMentionHighlight[]>;
suggestions?: () => Promise<IMentionSuggestion[]>;
};
realtimeConfig: TRealtimeConfig;
serverHandler?: TServerHandler;
user: TUserDetails;
};
export type TCollaborativeEditorProps = TCollaborativeEditorHookProps & {
type TCollaborativeEditorHookProps = TCollaborativeEditorHookCommonProps & {
onTransaction?: () => void;
embedHandler?: TEmbedConfig;
fileHandler: TFileHandler;
@ -44,7 +41,29 @@ export type TCollaborativeEditorProps = TCollaborativeEditorHookProps & {
tabIndex?: number;
};
export type TReadOnlyCollaborativeEditorProps = TCollaborativeEditorHookProps & {
type TCollaborativeReadOnlyEditorHookProps = TCollaborativeEditorHookCommonProps & {
fileHandler: Pick<TFileHandler, "getAssetSrc">;
forwardedRef?: React.MutableRefObject<EditorReadOnlyRefApi | null>;
};
export type TCollaborativeRichTextEditorHookProps = TCollaborativeEditorHookProps & {
onChange: (updatedDescription: Uint8Array) => void;
value: Uint8Array;
};
export type TCollaborativeRichTextReadOnlyEditorHookProps = TCollaborativeReadOnlyEditorHookProps & {
value: Uint8Array;
};
export type TCollaborativeDocumentEditorHookProps = TCollaborativeEditorHookProps & {
embedHandler?: TEmbedConfig;
realtimeConfig: TRealtimeConfig;
serverHandler?: TServerHandler;
user: TUserDetails;
};
export type TCollaborativeDocumentReadOnlyEditorHookProps = TCollaborativeReadOnlyEditorHookProps & {
realtimeConfig: TRealtimeConfig;
serverHandler?: TServerHandler;
user: TUserDetails;
};

View file

@ -132,6 +132,12 @@ export interface IRichTextEditor extends IEditorProps {
dragDropEnabled?: boolean;
}
export interface ICollaborativeRichTextEditor extends Omit<IEditorProps, "initialValue" | "onChange" | "value"> {
dragDropEnabled?: boolean;
onChange: (updatedDescription: Uint8Array) => void;
value: Uint8Array;
}
export interface ICollaborativeDocumentEditor
extends Omit<IEditorProps, "initialValue" | "onChange" | "onEnterKeyPress" | "value"> {
aiHandler?: TAIHandler;
@ -161,6 +167,10 @@ export type ILiteTextReadOnlyEditor = IReadOnlyEditorProps;
export type IRichTextReadOnlyEditor = IReadOnlyEditorProps;
export type ICollaborativeRichTextReadOnlyEditor = Omit<IReadOnlyEditorProps, "initialValue"> & {
value: Uint8Array;
};
export interface ICollaborativeDocumentReadOnlyEditor extends Omit<IReadOnlyEditorProps, "initialValue"> {
embedHandler: TEmbedConfig;
handleEditorReady?: (value: boolean) => void;

View file

@ -1,5 +1,5 @@
export * from "./ai";
export * from "./collaboration";
export * from "./collaboration-hook";
export * from "./config";
export * from "./editor";
export * from "./embed";

View file

@ -10,6 +10,8 @@ import "./styles/drag-drop.css";
export {
CollaborativeDocumentEditorWithRef,
CollaborativeDocumentReadOnlyEditorWithRef,
CollaborativeRichTextEditorWithRef,
CollaborativeRichTextReadOnlyEditorWithRef,
DocumentReadOnlyEditorWithRef,
LiteTextEditorWithRef,
LiteTextReadOnlyEditorWithRef,
@ -25,7 +27,7 @@ export * from "@/constants/common";
// helpers
export * from "@/helpers/common";
export * from "@/helpers/editor-commands";
export * from "@/helpers/yjs";
export * from "@/helpers/yjs-utils";
export * from "@/extensions/table/table";
// components

View file

@ -1 +1 @@
export * from "@/extensions/core-without-props";
export * from "@/helpers/yjs-utils";

View file

@ -50,6 +50,7 @@ export type IssueRelation = {
};
export type TIssue = TBaseIssue & {
description_binary?: string;
description_html?: string;
is_subscribed?: boolean;
parent?: Partial<TBaseIssue>;