diff --git a/apiserver/plane/app/serializers/draft.py b/apiserver/plane/app/serializers/draft.py
index b1df79f28e7..e07e416a75d 100644
--- a/apiserver/plane/app/serializers/draft.py
+++ b/apiserver/plane/app/serializers/draft.py
@@ -284,11 +284,9 @@ class Meta:
class DraftIssueDetailSerializer(DraftIssueSerializer):
description_html = serializers.CharField()
- description_binary = serializers.CharField()
class Meta(DraftIssueSerializer.Meta):
fields = DraftIssueSerializer.Meta.fields + [
"description_html",
- "description_binary",
]
read_only_fields = fields
diff --git a/apiserver/plane/app/serializers/issue.py b/apiserver/plane/app/serializers/issue.py
index ad1f9606b8a..b0904834f7b 100644
--- a/apiserver/plane/app/serializers/issue.py
+++ b/apiserver/plane/app/serializers/issue.py
@@ -1,6 +1,3 @@
-# Python imports
-import base64
-
# Django imports
from django.utils import timezone
from django.core.validators import URLValidator
@@ -735,31 +732,14 @@ class Meta:
read_only_fields = fields
-class Base64BinaryField(serializers.CharField):
- def to_representation(self, value):
- # Encode the binary data to base64 string for JSON response
- if value:
- return base64.b64encode(value).decode("utf-8")
- return None
-
- def to_internal_value(self, data):
- # Decode the base64 string to binary data when saving
- try:
- return base64.b64decode(data)
- except (TypeError, ValueError):
- raise serializers.ValidationError("Invalid base64-encoded data")
-
-
class IssueDetailSerializer(IssueSerializer):
description_html = serializers.CharField()
- description_binary = Base64BinaryField()
is_subscribed = serializers.BooleanField(read_only=True)
class Meta(IssueSerializer.Meta):
fields = IssueSerializer.Meta.fields + [
"description_html",
"is_subscribed",
- "description_binary",
]
read_only_fields = fields
diff --git a/apiserver/plane/app/serializers/workspace.py b/apiserver/plane/app/serializers/workspace.py
index 4f106022606..1a2b89bba61 100644
--- a/apiserver/plane/app/serializers/workspace.py
+++ b/apiserver/plane/app/serializers/workspace.py
@@ -66,7 +66,6 @@ class Meta:
class WorkspaceMemberMeSerializer(BaseSerializer):
draft_issue_count = serializers.IntegerField(read_only=True)
-
class Meta:
model = WorkspaceMember
fields = "__all__"
diff --git a/apiserver/plane/app/urls/intake.py b/apiserver/plane/app/urls/intake.py
index d4f160577e5..be2f3a053f4 100644
--- a/apiserver/plane/app/urls/intake.py
+++ b/apiserver/plane/app/urls/intake.py
@@ -92,14 +92,4 @@
),
name="inbox-issue",
),
- path(
- "workspaces//projects//inbox-issues//description/",
- IntakeIssueViewSet.as_view(
- {
- "get": "retrieve_description",
- "post": "update_description",
- }
- ),
- name="inbox-issue-description",
- ),
]
diff --git a/apiserver/plane/app/urls/issue.py b/apiserver/plane/app/urls/issue.py
index e20643546da..e8ad4408dfb 100644
--- a/apiserver/plane/app/urls/issue.py
+++ b/apiserver/plane/app/urls/issue.py
@@ -66,16 +66,6 @@
),
name="project-issue",
),
- path(
- "workspaces//projects//issues//description/",
- IssueViewSet.as_view(
- {
- "get": "retrieve_description",
- "post": "update_description",
- }
- ),
- name="project-issue-description",
- ),
path(
"workspaces//projects//issue-labels/",
LabelViewSet.as_view(
@@ -298,15 +288,6 @@
),
name="project-issue-archive-unarchive",
),
- path(
- "workspaces//projects//archived-issues//description/",
- IssueArchiveViewSet.as_view(
- {
- "get": "retrieve_description",
- }
- ),
- name="archive-issue-description",
- ),
## End Issue Archives
## Issue Relation
path(
diff --git a/apiserver/plane/app/urls/workspace.py b/apiserver/plane/app/urls/workspace.py
index 6481f5691cd..fb6f4c13acc 100644
--- a/apiserver/plane/app/urls/workspace.py
+++ b/apiserver/plane/app/urls/workspace.py
@@ -276,16 +276,6 @@
),
name="workspace-drafts-issues",
),
- path(
- "workspaces//draft-issues//description/",
- WorkspaceDraftIssueViewSet.as_view(
- {
- "get": "retrieve_description",
- "post": "update_description",
- }
- ),
- name="workspace-drafts-issues",
- ),
path(
"workspaces//draft-to-issue//",
WorkspaceDraftIssueViewSet.as_view({"post": "create_draft_to_issue"}),
diff --git a/apiserver/plane/app/views/intake/base.py b/apiserver/plane/app/views/intake/base.py
index 394957884c9..9e420c00720 100644
--- a/apiserver/plane/app/views/intake/base.py
+++ b/apiserver/plane/app/views/intake/base.py
@@ -1,7 +1,5 @@
# Python imports
import json
-import requests
-import base64
# Django import
from django.utils import timezone
@@ -11,9 +9,6 @@
from django.contrib.postgres.fields import ArrayField
from django.db.models import Value, UUIDField
from django.db.models.functions import Coalesce
-from django.http import StreamingHttpResponse
-from django.conf import settings
-
# Third party imports
from rest_framework import status
@@ -45,6 +40,7 @@
class IntakeViewSet(BaseViewSet):
+
serializer_class = IntakeSerializer
model = Intake
@@ -93,6 +89,7 @@ def destroy(self, request, slug, project_id, pk):
class IntakeIssueViewSet(BaseViewSet):
+
serializer_class = IntakeIssueSerializer
model = IntakeIssue
@@ -643,82 +640,3 @@ def destroy(self, request, slug, project_id, pk):
intake_issue.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
-
- @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
- def retrieve_description(self, request, slug, project_id, pk):
- issue = Issue.objects.filter(
- pk=pk, workspace__slug=slug, project_id=project_id
- ).first()
- if issue is None:
- return Response(
- {"error": "Issue not found"},
- status=404,
- )
- binary_data = issue.description_binary
-
- def stream_data():
- if binary_data:
- yield binary_data
- else:
- yield b""
-
- response = StreamingHttpResponse(
- stream_data(), content_type="application/octet-stream"
- )
- response["Content-Disposition"] = (
- 'attachment; filename="issue_description.bin"'
- )
- return response
-
- @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
- def update_description(self, request, slug, project_id, pk):
- issue = Issue.objects.get(
- workspace__slug=slug, project_id=project_id, pk=pk
- )
- base64_description = issue.description_binary
- # convert to base64 string
- if base64_description:
- base64_description = base64.b64encode(base64_description).decode(
- "utf-8"
- )
- data = {
- "original_document": base64_description,
- "updates": request.data.get("description_binary"),
- }
- base_url = f"{settings.LIVE_BASE_URL}/resolve-document-conflicts/"
- try:
- response = requests.post(base_url, json=data, headers=None)
- except requests.RequestException:
- return Response(
- {"error": "Failed to connect to the external service"},
- status=status.HTTP_502_BAD_GATEWAY,
- )
-
- if response.status_code == 200:
- issue.description = response.json().get(
- "description", issue.description
- )
- issue.description_html = response.json().get("description_html")
- response_description_binary = response.json().get(
- "description_binary"
- )
- issue.description_binary = base64.b64decode(
- response_description_binary
- )
- issue.save()
-
- def stream_data():
- if issue.description_binary:
- yield issue.description_binary
- else:
- yield b""
-
- response = StreamingHttpResponse(
- stream_data(), content_type="application/octet-stream"
- )
- response["Content-Disposition"] = (
- 'attachment; filename="issue_description.bin"'
- )
- return response
-
- return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
diff --git a/apiserver/plane/app/views/issue/archive.py b/apiserver/plane/app/views/issue/archive.py
index 3fa4e3c480d..292d9d61734 100644
--- a/apiserver/plane/app/views/issue/archive.py
+++ b/apiserver/plane/app/views/issue/archive.py
@@ -7,8 +7,6 @@
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
-from django.http import StreamingHttpResponse
-
# Third Party imports
from rest_framework import status
@@ -29,7 +27,7 @@
IssueLink,
IssueSubscriber,
IssueReaction,
- CycleIssue,
+ CycleIssue
)
from plane.utils.grouper import (
issue_group_values,
@@ -329,32 +327,6 @@ def unarchive(self, request, slug, project_id, pk=None):
return Response(status=status.HTTP_204_NO_CONTENT)
- @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
- def retrieve_description(self, request, slug, project_id, pk):
- issue = Issue.objects.filter(
- pk=pk, workspace__slug=slug, project_id=project_id
- ).first()
- if issue is None:
- return Response(
- {"error": "Issue not found"},
- status=404,
- )
- binary_data = issue.description_binary
-
- def stream_data():
- if binary_data:
- yield binary_data
- else:
- yield b""
-
- response = StreamingHttpResponse(
- stream_data(), content_type="application/octet-stream"
- )
- response["Content-Disposition"] = (
- 'attachment; filename="issue_description.bin"'
- )
- return response
-
class BulkArchiveIssuesEndpoint(BaseAPIView):
permission_classes = [
diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py
index 09b23f11a47..c30f889ba01 100644
--- a/apiserver/plane/app/views/issue/base.py
+++ b/apiserver/plane/app/views/issue/base.py
@@ -1,7 +1,5 @@
# Python imports
import json
-import requests
-import base64
# Django imports
from django.contrib.postgres.aggregates import ArrayAgg
@@ -22,10 +20,8 @@
)
from django.db.models.functions import Coalesce
from django.utils import timezone
-from django.http import StreamingHttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
-from django.conf import settings
# Third Party imports
from rest_framework import status
@@ -729,84 +725,6 @@ def destroy(self, request, slug, project_id, pk=None):
)
return Response(status=status.HTTP_204_NO_CONTENT)
- @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
- def retrieve_description(self, request, slug, project_id, pk):
- issue = Issue.issue_objects.filter(
- pk=pk, workspace__slug=slug, project_id=project_id
- ).first()
- if issue is None:
- return Response(
- {"error": "Issue not found"},
- status=404,
- )
- binary_data = issue.description_binary
-
- def stream_data():
- if binary_data:
- yield binary_data
- else:
- yield b""
-
- response = StreamingHttpResponse(
- stream_data(), content_type="application/octet-stream"
- )
- response["Content-Disposition"] = (
- 'attachment; filename="issue_description.bin"'
- )
- return response
-
- def update_description(self, request, slug, project_id, pk):
- issue = Issue.issue_objects.get(
- workspace__slug=slug, project_id=project_id, pk=pk
- )
- base64_description = issue.description_binary
- # convert to base64 string
- if base64_description:
- base64_description = base64.b64encode(base64_description).decode(
- "utf-8"
- )
- data = {
- "original_document": base64_description,
- "updates": request.data.get("description_binary"),
- }
- base_url = f"{settings.LIVE_BASE_URL}/resolve-document-conflicts/"
- try:
- response = requests.post(base_url, json=data, headers=None)
- except requests.RequestException:
- return Response(
- {"error": "Failed to connect to the external service"},
- status=status.HTTP_502_BAD_GATEWAY,
- )
-
- if response.status_code == 200:
- issue.description = response.json().get(
- "description", issue.description
- )
- issue.description_html = response.json().get("description_html")
- response_description_binary = response.json().get(
- "description_binary"
- )
- issue.description_binary = base64.b64decode(
- response_description_binary
- )
- issue.save()
-
- def stream_data():
- if issue.description_binary:
- yield issue.description_binary
- else:
- yield b""
-
- response = StreamingHttpResponse(
- stream_data(), content_type="application/octet-stream"
- )
- response["Content-Disposition"] = (
- 'attachment; filename="issue_description.bin"'
- )
- return response
-
- return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
class IssueUserDisplayPropertyEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
diff --git a/apiserver/plane/app/views/workspace/draft.py b/apiserver/plane/app/views/workspace/draft.py
index 1d68256c0ca..b2cb529fca6 100644
--- a/apiserver/plane/app/views/workspace/draft.py
+++ b/apiserver/plane/app/views/workspace/draft.py
@@ -1,7 +1,5 @@
# Python imports
import json
-import requests
-import base64
# Django imports
from django.utils import timezone
@@ -9,7 +7,6 @@
from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
-from django.http import StreamingHttpResponse
from django.db.models import (
Q,
UUIDField,
@@ -20,7 +17,6 @@
from django.db.models.functions import Coalesce
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
-from django.conf import settings
# Third Party imports
from rest_framework import status
@@ -354,78 +350,3 @@ def create_draft_to_issue(self, request, slug, draft_id):
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
-
- @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
- def retrieve_description(self, request, slug, pk):
- issue = DraftIssue.objects.filter(pk=pk, workspace__slug=slug).first()
- if issue is None:
- return Response(
- {"error": "Issue not found"},
- status=404,
- )
- binary_data = issue.description_binary
-
- def stream_data():
- if binary_data:
- yield binary_data
- else:
- yield b""
-
- response = StreamingHttpResponse(
- stream_data(), content_type="application/octet-stream"
- )
- response["Content-Disposition"] = (
- 'attachment; filename="draft_issue_description.bin"'
- )
- return response
-
- @allow_permission([ROLE.ADMIN, ROLE.MEMBER])
- def update_description(self, request, slug, pk):
- issue = DraftIssue.objects.get(workspace__slug=slug, pk=pk)
- base64_description = issue.description_binary
- # convert to base64 string
- if base64_description:
- base64_description = base64.b64encode(base64_description).decode(
- "utf-8"
- )
- data = {
- "original_document": base64_description,
- "updates": request.data.get("description_binary"),
- }
- base_url = f"{settings.LIVE_BASE_URL}/resolve-document-conflicts/"
- try:
- response = requests.post(base_url, json=data, headers=None)
- except requests.RequestException:
- return Response(
- {"error": "Failed to connect to the external service"},
- status=status.HTTP_502_BAD_GATEWAY,
- )
-
- if response.status_code == 200:
- issue.description = response.json().get(
- "description", issue.description
- )
- issue.description_html = response.json().get("description_html")
- response_description_binary = response.json().get(
- "description_binary"
- )
- issue.description_binary = base64.b64decode(
- response_description_binary
- )
- issue.save()
-
- def stream_data():
- if issue.description_binary:
- yield issue.description_binary
- else:
- yield b""
-
- response = StreamingHttpResponse(
- stream_data(), content_type="application/octet-stream"
- )
- response["Content-Disposition"] = (
- 'attachment; filename="issue_description.bin"'
- )
- return response
-
- return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
diff --git a/apiserver/plane/db/migrations/0085_intake_intakeissue_remove_inboxissue_created_by_and_more.py b/apiserver/plane/db/migrations/0085_intake_intakeissue_remove_inboxissue_created_by_and_more.py
index 16c4167cf66..36cf73bc541 100644
--- a/apiserver/plane/db/migrations/0085_intake_intakeissue_remove_inboxissue_created_by_and_more.py
+++ b/apiserver/plane/db/migrations/0085_intake_intakeissue_remove_inboxissue_created_by_and_more.py
@@ -1,7 +1,9 @@
# Generated by Django 4.2.15 on 2024-11-06 08:41
+from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
+import uuid
class Migration(migrations.Migration):
diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py
index 5e465cc46d8..a7096ef2a9f 100644
--- a/apiserver/plane/settings/common.py
+++ b/apiserver/plane/settings/common.py
@@ -381,7 +381,6 @@
ADMIN_BASE_URL = os.environ.get("ADMIN_BASE_URL", None)
SPACE_BASE_URL = os.environ.get("SPACE_BASE_URL", None)
APP_BASE_URL = os.environ.get("APP_BASE_URL")
-LIVE_BASE_URL = os.environ.get("LIVE_BASE_URL")
HARD_DELETE_AFTER_DAYS = int(os.environ.get("HARD_DELETE_AFTER_DAYS", 60))
diff --git a/apiserver/plane/space/urls/intake.py b/apiserver/plane/space/urls/intake.py
index 350157c950d..9f43a28098b 100644
--- a/apiserver/plane/space/urls/intake.py
+++ b/apiserver/plane/space/urls/intake.py
@@ -3,6 +3,7 @@
from plane.space.views import (
IntakeIssuePublicViewSet,
+ IssueVotePublicViewSet,
WorkspaceProjectDeployBoardEndpoint,
)
diff --git a/live/.prettierignore b/live/.prettierignore
deleted file mode 100644
index 09a5bb19de2..00000000000
--- a/live/.prettierignore
+++ /dev/null
@@ -1,6 +0,0 @@
-.next
-.vercel
-.tubro
-out/
-dist/
-node_modules/
\ No newline at end of file
diff --git a/live/.prettierrc b/live/.prettierrc
deleted file mode 100644
index 87d988f1b26..00000000000
--- a/live/.prettierrc
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "printWidth": 120,
- "tabWidth": 2,
- "trailingComma": "es5"
-}
diff --git a/live/src/core/helpers/page.ts b/live/src/core/helpers/page.ts
new file mode 100644
index 00000000000..4e79afe6b88
--- /dev/null
+++ b/live/src/core/helpers/page.ts
@@ -0,0 +1,59 @@
+import { getSchema } from "@tiptap/core";
+import { generateHTML, generateJSON } from "@tiptap/html";
+import { prosemirrorJSONToYDoc, yXmlFragmentToProseMirrorRootNode } from "y-prosemirror";
+import * as Y from "yjs"
+// plane editor
+import { CoreEditorExtensionsWithoutProps, DocumentEditorExtensionsWithoutProps } from "@plane/editor/lib";
+
+const DOCUMENT_EDITOR_EXTENSIONS = [
+ ...CoreEditorExtensionsWithoutProps,
+ ...DocumentEditorExtensionsWithoutProps,
+];
+const documentEditorSchema = getSchema(DOCUMENT_EDITOR_EXTENSIONS);
+
+export const getAllDocumentFormatsFromBinaryData = (description: Uint8Array): {
+ contentBinaryEncoded: string;
+ contentJSON: object;
+ contentHTML: string;
+} => {
+ // encode binary description data
+ const base64Data = Buffer.from(description).toString("base64");
+ 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,
+ };
+}
+
+export const getBinaryDataFromHTMLString = (descriptionHTML: string): {
+ contentBinary: Uint8Array
+} => {
+ // convert HTML to JSON
+ const contentJSON = generateJSON(
+ descriptionHTML ?? "",
+ 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 {
+ contentBinary: encodedData
+ }
+}
\ No newline at end of file
diff --git a/live/src/core/lib/page.ts b/live/src/core/lib/page.ts
index fb80402aaf6..c2110a2b8d3 100644
--- a/live/src/core/lib/page.ts
+++ b/live/src/core/lib/page.ts
@@ -1,8 +1,8 @@
-// plane editor
+// helpers
import {
- getAllDocumentFormatsFromDocumentEditorBinaryData,
- getBinaryDataFromDocumentEditorHTMLString,
-} from "@plane/editor/lib";
+ getAllDocumentFormatsFromBinaryData,
+ getBinaryDataFromHTMLString,
+} from "@/core/helpers/page.js";
// services
import { PageService } from "@/core/services/page.service.js";
import { manualLogger } from "../helpers/logger.js";
@@ -12,10 +12,12 @@ export const updatePageDescription = async (
params: URLSearchParams,
pageId: string,
updatedDescription: Uint8Array,
- cookie: string | undefined
+ cookie: string | undefined,
) => {
if (!(updatedDescription instanceof Uint8Array)) {
- throw new Error("Invalid updatedDescription: must be an instance of Uint8Array");
+ throw new Error(
+ "Invalid updatedDescription: must be an instance of Uint8Array",
+ );
}
const workspaceSlug = params.get("workspaceSlug")?.toString();
@@ -23,7 +25,7 @@ export const updatePageDescription = async (
if (!workspaceSlug || !projectId || !cookie) return;
const { contentBinaryEncoded, contentHTML, contentJSON } =
- getAllDocumentFormatsFromDocumentEditorBinaryData(updatedDescription);
+ getAllDocumentFormatsFromBinaryData(updatedDescription);
try {
const payload = {
description_binary: contentBinaryEncoded,
@@ -31,7 +33,13 @@ export const updatePageDescription = async (
description: contentJSON,
};
- await pageService.updateDescription(workspaceSlug, projectId, pageId, payload, cookie);
+ await pageService.updateDescription(
+ workspaceSlug,
+ projectId,
+ pageId,
+ payload,
+ cookie,
+ );
} catch (error) {
manualLogger.error("Update error:", error);
throw error;
@@ -42,16 +50,26 @@ const fetchDescriptionHTMLAndTransform = async (
workspaceSlug: string,
projectId: string,
pageId: string,
- cookie: string
+ cookie: string,
) => {
if (!workspaceSlug || !projectId || !cookie) return;
try {
- const pageDetails = await pageService.fetchDetails(workspaceSlug, projectId, pageId, cookie);
- const contentBinary = getBinaryDataFromDocumentEditorHTMLString(pageDetails.description_html ?? "");
+ const pageDetails = await pageService.fetchDetails(
+ workspaceSlug,
+ projectId,
+ pageId,
+ cookie,
+ );
+ const { contentBinary } = getBinaryDataFromHTMLString(
+ pageDetails.description_html ?? "",
+ );
return contentBinary;
} catch (error) {
- manualLogger.error("Error while transforming from HTML to Uint8Array", error);
+ manualLogger.error(
+ "Error while transforming from HTML to Uint8Array",
+ error,
+ );
throw error;
}
};
@@ -59,18 +77,28 @@ const fetchDescriptionHTMLAndTransform = async (
export const fetchPageDescriptionBinary = async (
params: URLSearchParams,
pageId: string,
- cookie: string | undefined
+ cookie: string | undefined,
) => {
const workspaceSlug = params.get("workspaceSlug")?.toString();
const projectId = params.get("projectId")?.toString();
if (!workspaceSlug || !projectId || !cookie) return null;
try {
- const response = await pageService.fetchDescriptionBinary(workspaceSlug, projectId, pageId, cookie);
+ const response = await pageService.fetchDescriptionBinary(
+ workspaceSlug,
+ projectId,
+ pageId,
+ cookie,
+ );
const binaryData = new Uint8Array(response);
if (binaryData.byteLength === 0) {
- const binary = await fetchDescriptionHTMLAndTransform(workspaceSlug, projectId, pageId, cookie);
+ const binary = await fetchDescriptionHTMLAndTransform(
+ workspaceSlug,
+ projectId,
+ pageId,
+ cookie,
+ );
if (binary) {
return binary;
}
diff --git a/live/src/core/resolve-conflicts.ts b/live/src/core/resolve-conflicts.ts
deleted file mode 100644
index ffaab707c1d..00000000000
--- a/live/src/core/resolve-conflicts.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-// plane editor
-import {
- applyUpdates,
- convertBase64StringToBinaryData,
- getAllDocumentFormatsFromRichTextEditorBinaryData,
-} from "@plane/editor/lib";
-
-export type TResolveConflictsRequestBody = {
- original_document: string;
- updates: string;
-};
-
-export type TResolveConflictsResponse = {
- description_binary: string;
- description_html: string;
- description: object;
-};
-
-export const resolveDocumentConflicts = (body: TResolveConflictsRequestBody): TResolveConflictsResponse => {
- const { original_document, updates } = body;
- try {
- // convert from base64 to buffer
- const originalDocumentBuffer = original_document ? convertBase64StringToBinaryData(original_document) : null;
- const updatesBuffer = updates ? convertBase64StringToBinaryData(updates) : null;
- // decode req.body
- const decodedOriginalDocument = originalDocumentBuffer ? new Uint8Array(originalDocumentBuffer) : new Uint8Array();
- const decodedUpdates = updatesBuffer ? new Uint8Array(updatesBuffer) : new Uint8Array();
- // resolve conflicts
- let resolvedDocument: Uint8Array;
- if (decodedOriginalDocument.length === 0) {
- // use updates to create the document id original_description is null
- resolvedDocument = applyUpdates(decodedUpdates);
- } else {
- // use original document and updates to resolve conflicts
- resolvedDocument = applyUpdates(decodedOriginalDocument, decodedUpdates);
- }
-
- const { contentBinaryEncoded, contentHTML, contentJSON } =
- getAllDocumentFormatsFromRichTextEditorBinaryData(resolvedDocument);
-
- return {
- description_binary: contentBinaryEncoded,
- description_html: contentHTML,
- description: contentJSON,
- };
- } catch (error) {
- throw new Error("Internal server error");
- }
-};
diff --git a/live/src/server.ts b/live/src/server.ts
index ff9977e0a37..1868b86c198 100644
--- a/live/src/server.ts
+++ b/live/src/server.ts
@@ -5,13 +5,16 @@ import expressWs from "express-ws";
import * as Sentry from "@sentry/node";
import compression from "compression";
import helmet from "helmet";
+
+// cors
import cors from "cors";
+
// core hocuspocus server
import { getHocusPocusServer } from "@/core/hocuspocus-server.js";
+
// helpers
-import { errorHandler } from "@/core/helpers/error-handler.js";
import { logger, manualLogger } from "@/core/helpers/logger.js";
-import { resolveDocumentConflicts, TResolveConflictsRequestBody } from "@/core/resolve-conflicts.js";
+import { errorHandler } from "@/core/helpers/error-handler.js";
const app = express();
expressWs(app);
@@ -26,7 +29,7 @@ app.use(
compression({
level: 6,
threshold: 5 * 1000,
- })
+ }),
);
// Logging middleware
@@ -59,25 +62,6 @@ router.ws("/collaboration", (ws, req) => {
}
});
-app.post("/resolve-document-conflicts", (req, res) => {
- const { original_document, updates } = req.body as TResolveConflictsRequestBody;
- try {
- if (original_document === undefined || updates === undefined) {
- res.status(400).send({
- message: "Missing required fields",
- });
- return;
- }
- const resolvedDocument = resolveDocumentConflicts(req.body);
- res.status(200).json(resolvedDocument);
- } catch (error) {
- manualLogger.error("Error in /resolve-document-conflicts endpoint:", error);
- res.status(500).send({
- message: "Internal server error",
- });
- }
-});
-
app.use(process.env.LIVE_BASE_PATH || "/live", router);
app.use((_req, res) => {
@@ -98,7 +82,9 @@ const gracefulShutdown = async () => {
try {
// Close the HocusPocus server WebSocket connections
await HocusPocusServer.destroy();
- manualLogger.info("HocusPocus server WebSocket connections closed gracefully.");
+ manualLogger.info(
+ "HocusPocus server WebSocket connections closed gracefully.",
+ );
// Close the Express server
liveServer.close(() => {
diff --git a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx
index 6a9b9db532c..cd7d6f35489 100644
--- a/packages/editor/src/core/components/editors/document/collaborative-editor.tsx
+++ b/packages/editor/src/core/components/editors/document/collaborative-editor.tsx
@@ -8,7 +8,7 @@ import { IssueWidget } from "@/extensions";
// helpers
import { getEditorClassNames } from "@/helpers/common";
// hooks
-import { useCollaborativeDocumentEditor } from "@/hooks/use-collaborative-document-editor";
+import { useCollaborativeEditor } from "@/hooks/use-collaborative-editor";
// types
import { EditorRefApi, ICollaborativeDocumentEditor } from "@/types";
@@ -43,7 +43,7 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => {
}
// use document editor
- const { editor, hasServerConnectionFailed, hasServerSynced } = useCollaborativeDocumentEditor({
+ const { editor, hasServerConnectionFailed, hasServerSynced } = useCollaborativeEditor({
onTransaction,
disabledExtensions,
editorClassName,
diff --git a/packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx b/packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx
index 90de2e84c62..aa925abece4 100644
--- a/packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx
+++ b/packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx
@@ -8,7 +8,7 @@ import { IssueWidget } from "@/extensions";
// helpers
import { getEditorClassNames } from "@/helpers/common";
// hooks
-import { useCollaborativeDocumentReadOnlyEditor } from "@/hooks/use-collaborative-document-read-only-editor";
+import { useReadOnlyCollaborativeEditor } from "@/hooks/use-read-only-collaborative-editor";
// types
import { EditorReadOnlyRefApi, ICollaborativeDocumentReadOnlyEditor } from "@/types";
@@ -36,7 +36,7 @@ const CollaborativeDocumentReadOnlyEditor = (props: ICollaborativeDocumentReadOn
);
}
- const { editor, hasServerConnectionFailed, hasServerSynced } = useCollaborativeDocumentReadOnlyEditor({
+ const { editor, hasServerConnectionFailed, hasServerSynced } = useReadOnlyCollaborativeEditor({
editorClassName,
extensions,
fileHandler,
diff --git a/packages/editor/src/core/components/editors/editor-wrapper.tsx b/packages/editor/src/core/components/editors/editor-wrapper.tsx
index a7332f370fb..33f011535c5 100644
--- a/packages/editor/src/core/components/editors/editor-wrapper.tsx
+++ b/packages/editor/src/core/components/editors/editor-wrapper.tsx
@@ -1,4 +1,4 @@
-import { AnyExtension, Editor } from "@tiptap/core";
+import { Editor, Extension } 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: AnyExtension[];
+ extensions: Extension[];
};
export const EditorWrapper: React.FC = (props) => {
diff --git a/packages/editor/src/core/components/editors/rich-text/collaborative-editor.tsx b/packages/editor/src/core/components/editors/rich-text/collaborative-editor.tsx
deleted file mode 100644
index a96daef3325..00000000000
--- a/packages/editor/src/core/components/editors/rich-text/collaborative-editor.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-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 (
-
-
-
-
-
-
- );
-};
-
-const CollaborativeRichTextEditorWithRef = React.forwardRef(
- (props, ref) => (
- } />
- )
-);
-
-CollaborativeRichTextEditorWithRef.displayName = "CollaborativeRichTextEditorWithRef";
-
-export { CollaborativeRichTextEditorWithRef };
diff --git a/packages/editor/src/core/components/editors/rich-text/collaborative-read-only-editor.tsx b/packages/editor/src/core/components/editors/rich-text/collaborative-read-only-editor.tsx
deleted file mode 100644
index 050d97cae61..00000000000
--- a/packages/editor/src/core/components/editors/rich-text/collaborative-read-only-editor.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-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 (
-
-
-
-
-
-
- );
-};
-
-const CollaborativeRichTextReadOnlyEditorWithRef = React.forwardRef<
- EditorReadOnlyRefApi,
- ICollaborativeRichTextReadOnlyEditor
->((props, ref) => (
- }
- />
-));
-
-CollaborativeRichTextReadOnlyEditorWithRef.displayName = "CollaborativeRichTextReadOnlyEditorWithRef";
-
-export { CollaborativeRichTextReadOnlyEditorWithRef };
diff --git a/packages/editor/src/core/components/editors/rich-text/index.ts b/packages/editor/src/core/components/editors/rich-text/index.ts
index 3053a54112d..b2ba8682a3c 100644
--- a/packages/editor/src/core/components/editors/rich-text/index.ts
+++ b/packages/editor/src/core/components/editors/rich-text/index.ts
@@ -1,4 +1,2 @@
-export * from "./collaborative-editor";
-export * from "./collaborative-read-only-editor";
export * from "./editor";
export * from "./read-only-editor";
diff --git a/packages/editor/src/core/helpers/yjs-utils.ts b/packages/editor/src/core/helpers/yjs-utils.ts
deleted file mode 100644
index dd1b9f2fa2f..00000000000
--- a/packages/editor/src/core/helpers/yjs-utils.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-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 ?? "", 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 ?? "", 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,
- };
-};
diff --git a/packages/editor/src/core/helpers/yjs.ts b/packages/editor/src/core/helpers/yjs.ts
new file mode 100644
index 00000000000..ffd9367107d
--- /dev/null
+++ b/packages/editor/src/core/helpers/yjs.ts
@@ -0,0 +1,16 @@
+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;
+};
diff --git a/packages/editor/src/core/hooks/use-collaborative-document-editor.ts b/packages/editor/src/core/hooks/use-collaborative-editor.ts
similarity index 93%
rename from packages/editor/src/core/hooks/use-collaborative-document-editor.ts
rename to packages/editor/src/core/hooks/use-collaborative-editor.ts
index d286db9625b..5bee8c0c3f5 100644
--- a/packages/editor/src/core/hooks/use-collaborative-document-editor.ts
+++ b/packages/editor/src/core/hooks/use-collaborative-editor.ts
@@ -9,9 +9,9 @@ import { useEditor } from "@/hooks/use-editor";
// plane editor extensions
import { DocumentEditorAdditionalExtensions } from "@/plane-editor/extensions";
// types
-import { TCollaborativeDocumentEditorHookProps } from "@/types";
+import { TCollaborativeEditorProps } from "@/types";
-export const useCollaborativeDocumentEditor = (props: TCollaborativeDocumentEditorHookProps) => {
+export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
const {
onTransaction,
disabledExtensions,
@@ -102,7 +102,7 @@ export const useCollaborativeDocumentEditor = (props: TCollaborativeDocumentEdit
forwardedRef,
mentionHandler,
placeholder,
- providerDocument: provider.document,
+ provider,
tabIndex,
});
diff --git a/packages/editor/src/core/hooks/use-collaborative-rich-text-editor.ts b/packages/editor/src/core/hooks/use-collaborative-rich-text-editor.ts
deleted file mode 100644
index e9a5106d44c..00000000000
--- a/packages/editor/src/core/hooks/use-collaborative-rich-text-editor.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-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,
- };
-};
diff --git a/packages/editor/src/core/hooks/use-collaborative-rich-text-read-only-editor.ts b/packages/editor/src/core/hooks/use-collaborative-rich-text-read-only-editor.ts
deleted file mode 100644
index be5b915fdbc..00000000000
--- a/packages/editor/src/core/hooks/use-collaborative-rich-text-read-only-editor.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-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,
- };
-};
diff --git a/packages/editor/src/core/hooks/use-editor.ts b/packages/editor/src/core/hooks/use-editor.ts
index 579dd816f4c..eef72797cee 100644
--- a/packages/editor/src/core/hooks/use-editor.ts
+++ b/packages/editor/src/core/hooks/use-editor.ts
@@ -1,4 +1,5 @@
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";
@@ -35,7 +36,7 @@ export interface CustomEditorProps {
onTransaction?: () => void;
autofocus?: boolean;
placeholder?: string | ((isFocused: boolean, value: string) => string);
- providerDocument?: Y.Doc;
+ provider?: HocuspocusProvider;
tabIndex?: number;
// undefined when prop is not passed, null if intentionally passed to stop
// swr syncing
@@ -57,7 +58,7 @@ export const useEditor = (props: CustomEditorProps) => {
onChange,
onTransaction,
placeholder,
- providerDocument,
+ provider,
tabIndex,
value,
autofocus = false,
@@ -205,7 +206,7 @@ export const useEditor = (props: CustomEditorProps) => {
return markdownOutput;
},
getDocument: () => {
- const documentBinary = providerDocument ? Y.encodeStateAsUpdate(providerDocument) : null;
+ const documentBinary = provider?.document ? Y.encodeStateAsUpdate(provider?.document) : null;
const documentHTML = editorRef.current?.getHTML() ?? "";
const documentJSON = editorRef.current?.getJSON() ?? null;
@@ -283,7 +284,7 @@ export const useEditor = (props: CustomEditorProps) => {
words: editorRef?.current?.storage?.characterCount?.words?.() ?? 0,
}),
setProviderDocument: (value) => {
- const document = providerDocument;
+ const document = provider?.document;
if (!document) return;
Y.applyUpdate(document, value);
},
diff --git a/packages/editor/src/core/hooks/use-collaborative-document-read-only-editor.ts b/packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts
similarity index 90%
rename from packages/editor/src/core/hooks/use-collaborative-document-read-only-editor.ts
rename to packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts
index 274a40763d6..d4081922973 100644
--- a/packages/editor/src/core/hooks/use-collaborative-document-read-only-editor.ts
+++ b/packages/editor/src/core/hooks/use-read-only-collaborative-editor.ts
@@ -7,9 +7,9 @@ import { HeadingListExtension } from "@/extensions";
// hooks
import { useReadOnlyEditor } from "@/hooks/use-read-only-editor";
// types
-import { TCollaborativeDocumentReadOnlyEditorHookProps } from "@/types";
+import { TReadOnlyCollaborativeEditorProps } from "@/types";
-export const useCollaborativeDocumentReadOnlyEditor = (props: TCollaborativeDocumentReadOnlyEditorHookProps) => {
+export const useReadOnlyCollaborativeEditor = (props: TReadOnlyCollaborativeEditorProps) => {
const {
editorClassName,
editorProps = {},
@@ -79,7 +79,7 @@ export const useCollaborativeDocumentReadOnlyEditor = (props: TCollaborativeDocu
forwardedRef,
handleEditorReady,
mentionHandler,
- providerDocument: provider.document,
+ provider,
});
return {
diff --git a/packages/editor/src/core/hooks/use-read-only-editor.ts b/packages/editor/src/core/hooks/use-read-only-editor.ts
index cde6a8937a9..23ce023adcd 100644
--- a/packages/editor/src/core/hooks/use-read-only-editor.ts
+++ b/packages/editor/src/core/hooks/use-read-only-editor.ts
@@ -1,4 +1,5 @@
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";
@@ -23,7 +24,7 @@ interface CustomReadOnlyEditorProps {
mentionHandler: {
highlights: () => Promise;
};
- providerDocument?: Y.Doc;
+ provider?: HocuspocusProvider;
}
export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
@@ -36,7 +37,7 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
fileHandler,
handleEditorReady,
mentionHandler,
- providerDocument,
+ provider,
} = props;
const editor = useCustomEditor({
@@ -85,7 +86,7 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
return markdownOutput;
},
getDocument: () => {
- const documentBinary = providerDocument ? Y.encodeStateAsUpdate(providerDocument) : null;
+ const documentBinary = provider?.document ? Y.encodeStateAsUpdate(provider?.document) : null;
const documentHTML = editorRef.current?.getHTML() ?? "";
const documentJSON = editorRef.current?.getJSON() ?? null;
diff --git a/packages/editor/src/core/providers/custom-collaboration-provider.ts b/packages/editor/src/core/providers/custom-collaboration-provider.ts
deleted file mode 100644
index 036b15fa152..00000000000
--- a/packages/editor/src/core/providers/custom-collaboration-provider.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-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> &
- Partial;
-
-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 = {}): 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);
- }
-}
diff --git a/packages/editor/src/core/providers/index.ts b/packages/editor/src/core/providers/index.ts
deleted file mode 100644
index 36e7996394a..00000000000
--- a/packages/editor/src/core/providers/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./custom-collaboration-provider";
diff --git a/packages/editor/src/core/types/collaboration-hook.ts b/packages/editor/src/core/types/collaboration.ts
similarity index 60%
rename from packages/editor/src/core/types/collaboration-hook.ts
rename to packages/editor/src/core/types/collaboration.ts
index 5796df21900..8609995ed83 100644
--- a/packages/editor/src/core/types/collaboration-hook.ts
+++ b/packages/editor/src/core/types/collaboration.ts
@@ -19,7 +19,7 @@ export type TServerHandler = {
onServerError?: () => void;
};
-type TCollaborativeEditorHookCommonProps = {
+type TCollaborativeEditorHookProps = {
disabledExtensions?: TExtensions[];
editorClassName: string;
editorProps?: EditorProps;
@@ -30,9 +30,12 @@ type TCollaborativeEditorHookCommonProps = {
highlights: () => Promise;
suggestions?: () => Promise;
};
+ realtimeConfig: TRealtimeConfig;
+ serverHandler?: TServerHandler;
+ user: TUserDetails;
};
-type TCollaborativeEditorHookProps = TCollaborativeEditorHookCommonProps & {
+export type TCollaborativeEditorProps = TCollaborativeEditorHookProps & {
onTransaction?: () => void;
embedHandler?: TEmbedConfig;
fileHandler: TFileHandler;
@@ -41,29 +44,7 @@ type TCollaborativeEditorHookProps = TCollaborativeEditorHookCommonProps & {
tabIndex?: number;
};
-type TCollaborativeReadOnlyEditorHookProps = TCollaborativeEditorHookCommonProps & {
+export type TReadOnlyCollaborativeEditorProps = TCollaborativeEditorHookProps & {
fileHandler: Pick;
forwardedRef?: React.MutableRefObject;
};
-
-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;
-};
diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts
index 4b2bf3e38bf..53aae1f265d 100644
--- a/packages/editor/src/core/types/editor.ts
+++ b/packages/editor/src/core/types/editor.ts
@@ -132,12 +132,6 @@ export interface IRichTextEditor extends IEditorProps {
dragDropEnabled?: boolean;
}
-export interface ICollaborativeRichTextEditor extends Omit {
- dragDropEnabled?: boolean;
- onChange: (updatedDescription: Uint8Array) => void;
- value: Uint8Array;
-}
-
export interface ICollaborativeDocumentEditor
extends Omit {
aiHandler?: TAIHandler;
@@ -167,10 +161,6 @@ export type ILiteTextReadOnlyEditor = IReadOnlyEditorProps;
export type IRichTextReadOnlyEditor = IReadOnlyEditorProps;
-export type ICollaborativeRichTextReadOnlyEditor = Omit & {
- value: Uint8Array;
-};
-
export interface ICollaborativeDocumentReadOnlyEditor extends Omit {
embedHandler: TEmbedConfig;
handleEditorReady?: (value: boolean) => void;
diff --git a/packages/editor/src/core/types/index.ts b/packages/editor/src/core/types/index.ts
index b4c4ad3625a..8da9ed276e5 100644
--- a/packages/editor/src/core/types/index.ts
+++ b/packages/editor/src/core/types/index.ts
@@ -1,5 +1,5 @@
export * from "./ai";
-export * from "./collaboration-hook";
+export * from "./collaboration";
export * from "./config";
export * from "./editor";
export * from "./embed";
diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts
index eb59deade40..ed7d9134698 100644
--- a/packages/editor/src/index.ts
+++ b/packages/editor/src/index.ts
@@ -10,8 +10,6 @@ import "./styles/drag-drop.css";
export {
CollaborativeDocumentEditorWithRef,
CollaborativeDocumentReadOnlyEditorWithRef,
- CollaborativeRichTextEditorWithRef,
- CollaborativeRichTextReadOnlyEditorWithRef,
DocumentReadOnlyEditorWithRef,
LiteTextEditorWithRef,
LiteTextReadOnlyEditorWithRef,
@@ -27,7 +25,7 @@ export * from "@/constants/common";
// helpers
export * from "@/helpers/common";
export * from "@/helpers/editor-commands";
-export * from "@/helpers/yjs-utils";
+export * from "@/helpers/yjs";
export * from "@/extensions/table/table";
// components
diff --git a/packages/editor/src/lib.ts b/packages/editor/src/lib.ts
index 2f684724bc2..e14c40127fb 100644
--- a/packages/editor/src/lib.ts
+++ b/packages/editor/src/lib.ts
@@ -1 +1 @@
-export * from "@/helpers/yjs-utils";
+export * from "@/extensions/core-without-props";
diff --git a/packages/types/src/issues/issue.d.ts b/packages/types/src/issues/issue.d.ts
index 1d9580fd1b4..ae4a98d63f0 100644
--- a/packages/types/src/issues/issue.d.ts
+++ b/packages/types/src/issues/issue.d.ts
@@ -50,7 +50,6 @@ export type IssueRelation = {
};
export type TIssue = TBaseIssue & {
- description_binary?: string;
description_html?: string;
is_subscribed?: boolean;
parent?: Partial;
diff --git a/web/core/components/core/modals/gpt-assistant-popover.tsx b/web/core/components/core/modals/gpt-assistant-popover.tsx
index 99ae0e5863a..0056977ed6b 100644
--- a/web/core/components/core/modals/gpt-assistant-popover.tsx
+++ b/web/core/components/core/modals/gpt-assistant-popover.tsx
@@ -9,7 +9,7 @@ import { Popover, Transition } from "@headlessui/react";
// ui
import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
// components
-import { RichTextReadOnlyEditor } from "@/components/editor";
+import { RichTextReadOnlyEditor } from "@/components/editor/rich-text-editor/rich-text-read-only-editor";
// services
import { AIService } from "@/services/ai.service";
diff --git a/web/core/components/editor/rich-text-editor/collaborative-editor.tsx b/web/core/components/editor/rich-text-editor/collaborative-editor.tsx
deleted file mode 100644
index 103d0d77fce..00000000000
--- a/web/core/components/editor/rich-text-editor/collaborative-editor.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import React, { forwardRef } from "react";
-// editor
-import { CollaborativeRichTextEditorWithRef, EditorRefApi, ICollaborativeRichTextEditor } from "@plane/editor";
-// types
-import { IUserLite } from "@plane/types";
-// helpers
-import { cn } from "@/helpers/common.helper";
-import { getEditorFileHandlers } from "@/helpers/editor.helper";
-// hooks
-import { useMember, useMention, useUser } from "@/hooks/store";
-// plane web hooks
-import { useFileSize } from "@/plane-web/hooks/use-file-size";
-
-interface Props extends Omit {
- key: string;
- projectId: string;
- uploadFile: (file: File) => Promise;
- workspaceId: string;
- workspaceSlug: string;
-}
-
-export const CollaborativeRichTextEditor = forwardRef((props, ref) => {
- const { containerClassName, workspaceSlug, workspaceId, projectId, uploadFile, ...rest } = props;
- // store hooks
- const { data: currentUser } = useUser();
- const {
- getUserDetails,
- project: { getProjectMemberIds },
- } = useMember();
- // derived values
- const projectMemberIds = getProjectMemberIds(projectId);
- const projectMemberDetails = projectMemberIds?.map((id) => getUserDetails(id) as IUserLite);
- // use-mention
- const { mentionHighlights, mentionSuggestions } = useMention({
- workspaceSlug,
- projectId,
- members: projectMemberDetails,
- user: currentUser,
- });
- // file size
- const { maxFileSize } = useFileSize();
-
- return (
-
- );
-});
-
-CollaborativeRichTextEditor.displayName = "CollaborativeRichTextEditor";
diff --git a/web/core/components/editor/rich-text-editor/collaborative-read-only-editor.tsx b/web/core/components/editor/rich-text-editor/collaborative-read-only-editor.tsx
deleted file mode 100644
index 2e80f86c75e..00000000000
--- a/web/core/components/editor/rich-text-editor/collaborative-read-only-editor.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import React from "react";
-// editor
-import {
- CollaborativeRichTextReadOnlyEditorWithRef,
- EditorReadOnlyRefApi,
- ICollaborativeRichTextReadOnlyEditor,
-} from "@plane/editor";
-// plane ui
-import { Loader } from "@plane/ui";
-// helpers
-import { cn } from "@/helpers/common.helper";
-import { getReadOnlyEditorFileHandlers } from "@/helpers/editor.helper";
-// hooks
-import { useMention } from "@/hooks/store";
-import { useIssueDescription } from "@/hooks/use-issue-description";
-
-type RichTextReadOnlyEditorWrapperProps = Omit<
- ICollaborativeRichTextReadOnlyEditor,
- "fileHandler" | "mentionHandler" | "value"
-> & {
- descriptionBinary: string | null;
- descriptionHTML: string;
- projectId?: string;
- workspaceSlug: string;
-};
-
-export const CollaborativeRichTextReadOnlyEditor = React.forwardRef<
- EditorReadOnlyRefApi,
- RichTextReadOnlyEditorWrapperProps
->(({ descriptionBinary: savedDescriptionBinary, descriptionHTML, projectId, workspaceSlug, ...props }, ref) => {
- const { mentionHighlights } = useMention({});
-
- const { descriptionBinary } = useIssueDescription({
- descriptionBinary: savedDescriptionBinary,
- descriptionHTML,
- });
-
- if (!descriptionBinary)
- return (
-
-
-
- );
-
- return (
-
- );
-});
-
-CollaborativeRichTextReadOnlyEditor.displayName = "CollaborativeRichTextReadOnlyEditor";
diff --git a/web/core/components/editor/rich-text-editor/index.ts b/web/core/components/editor/rich-text-editor/index.ts
index 3053a54112d..f185d0054e8 100644
--- a/web/core/components/editor/rich-text-editor/index.ts
+++ b/web/core/components/editor/rich-text-editor/index.ts
@@ -1,4 +1,2 @@
-export * from "./collaborative-editor";
-export * from "./collaborative-read-only-editor";
-export * from "./editor";
-export * from "./read-only-editor";
+export * from "./rich-text-editor";
+export * from "./rich-text-read-only-editor";
diff --git a/web/core/components/editor/rich-text-editor/editor.tsx b/web/core/components/editor/rich-text-editor/rich-text-editor.tsx
similarity index 100%
rename from web/core/components/editor/rich-text-editor/editor.tsx
rename to web/core/components/editor/rich-text-editor/rich-text-editor.tsx
diff --git a/web/core/components/editor/rich-text-editor/read-only-editor.tsx b/web/core/components/editor/rich-text-editor/rich-text-read-only-editor.tsx
similarity index 100%
rename from web/core/components/editor/rich-text-editor/read-only-editor.tsx
rename to web/core/components/editor/rich-text-editor/rich-text-read-only-editor.tsx
diff --git a/web/core/components/inbox/content/issue-root.tsx b/web/core/components/inbox/content/issue-root.tsx
index f547a552b87..2673245b0f1 100644
--- a/web/core/components/inbox/content/issue-root.tsx
+++ b/web/core/components/inbox/content/issue-root.tsx
@@ -3,8 +3,10 @@
import { Dispatch, SetStateAction, useEffect, useMemo } from "react";
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
+// plane types
+import { TIssue } from "@plane/types";
// plane ui
-import { TOAST_TYPE, setToast } from "@plane/ui";
+import { Loader, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { InboxIssueContentProperties } from "@/components/inbox/content";
import {
@@ -20,12 +22,11 @@ import { ISSUE_ARCHIVED, ISSUE_DELETED } from "@/constants/event-tracker";
// helpers
import { getTextContent } from "@/helpers/editor.helper";
// hooks
-import { useEventTracker, useIssueDetail, useProject, useUser } from "@/hooks/store";
+import { useEventTracker, useIssueDetail, useProject, useProjectInbox, useUser } from "@/hooks/store";
import useReloadConfirmations from "@/hooks/use-reload-confirmation";
// store types
import { DeDupeIssuePopoverRoot } from "@/plane-web/components/de-dupe";
import { useDebouncedDuplicateIssues } from "@/plane-web/hooks/use-debounced-duplicate-issues";
-// store
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
type Props = {
@@ -44,6 +45,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
const { data: currentUser } = useUser();
const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
const { captureIssueEvent } = useEventTracker();
+ const { loader } = useProjectInbox();
const { getProjectById } = useProject();
const { removeIssue, archiveIssue } = useIssueDetail();
@@ -58,7 +60,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
}
}, [isSubmitting, setShowAlert, setIsSubmitting]);
- // derived values
+ // dervied values
const issue = inboxIssue.issue;
const projectDetails = issue?.project_id ? getProjectById(issue?.project_id) : undefined;
@@ -73,8 +75,12 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
const issueOperations: TIssueOperations = useMemo(
() => ({
- fetch: async () => {},
- remove: async (_workspaceSlug, _projectId, _issueId) => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars, arrow-body-style
+ fetch: async (_workspaceSlug: string, _projectId: string, _issueId: string) => {
+ return;
+ },
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars, arrow-body-style
+ remove: async (_workspaceSlug: string, _projectId: string, _issueId: string) => {
try {
await removeIssue(workspaceSlug, projectId, _issueId);
setToast({
@@ -101,7 +107,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
});
}
},
- update: async (_workspaceSlug, _projectId, _issueId, data) => {
+ update: async (_workspaceSlug: string, _projectId: string, _issueId: string, data: Partial) => {
try {
await inboxIssue.updateIssue(data);
captureIssueEvent({
@@ -113,7 +119,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
},
path: pathname,
});
- } catch {
+ } catch (error) {
setToast({
title: "Issue update failed",
type: TOAST_TYPE.ERROR,
@@ -130,14 +136,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
});
}
},
- updateDescription: async (_workspaceSlug, _projectId, _issueId, descriptionBinary) => {
- try {
- return await inboxIssue.updateIssueDescription(descriptionBinary);
- } catch {
- throw new Error("Failed to update issue description");
- }
- },
- archive: async (workspaceSlug, projectId, issueId) => {
+ archive: async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
await archiveIssue(workspaceSlug, projectId, issueId);
captureIssueEvent({
@@ -155,7 +154,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
}
},
}),
- [archiveIssue, captureIssueEvent, inboxIssue, pathname, projectId, removeIssue, workspaceSlug]
+ [inboxIssue]
);
if (!issue?.project_id || !issue?.id) return <>>;
@@ -185,20 +184,21 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
containerClassName="-ml-3"
/>
- {issue.description_binary !== undefined && (
+ {loader === "issue-loading" ? (
+
+
+
+ ) : (
"}
- disabled={!isEditable}
- updateDescription={async (data) =>
- await issueOperations.updateDescription(workspaceSlug, projectId, issue.id ?? "", data)
- }
- issueId={issue.id}
+ workspaceSlug={workspaceSlug}
projectId={issue.project_id}
+ issueId={issue.id}
+ swrIssueDescription={issue.description_html ?? ""}
+ initialValue={issue.description_html ?? ""}
+ disabled={!isEditable}
+ issueOperations={issueOperations}
setIsSubmitting={(value) => setIsSubmitting(value)}
- workspaceSlug={workspaceSlug}
+ containerClassName="-ml-3 border-none"
/>
)}
diff --git a/web/core/components/inbox/modals/create-modal/issue-description.tsx b/web/core/components/inbox/modals/create-modal/issue-description.tsx
index 4cf7b3f932c..b9bad6c11ac 100644
--- a/web/core/components/inbox/modals/create-modal/issue-description.tsx
+++ b/web/core/components/inbox/modals/create-modal/issue-description.tsx
@@ -10,7 +10,7 @@ import { EFileAssetType } from "@plane/types/src/enums";
// ui
import { Loader } from "@plane/ui";
// components
-import { RichTextEditor } from "@/components/editor";
+import { RichTextEditor } from "@/components/editor/rich-text-editor/rich-text-editor";
// constants
import { ETabIndices } from "@/constants/tab-indices";
// helpers
diff --git a/web/core/components/issues/description-input.tsx b/web/core/components/issues/description-input.tsx
index 7e6229d9e42..8c18618c506 100644
--- a/web/core/components/issues/description-input.tsx
+++ b/web/core/components/issues/description-input.tsx
@@ -1,144 +1,157 @@
"use client";
-import { FC, useCallback, useRef } from "react";
+import { FC, useCallback, useEffect, useState } from "react";
import debounce from "lodash/debounce";
import { observer } from "mobx-react";
-// plane editor
-import { convertBinaryDataToBase64String, EditorRefApi } from "@plane/editor";
+import { Controller, useForm } from "react-hook-form";
// types
+import { TIssue } from "@plane/types";
import { EFileAssetType } from "@plane/types/src/enums";
-// plane ui
+// ui
import { Loader } from "@plane/ui";
// components
-import { CollaborativeRichTextEditor, CollaborativeRichTextReadOnlyEditor } from "@/components/editor";
+import { RichTextEditor, RichTextReadOnlyEditor } from "@/components/editor";
+import { TIssueOperations } from "@/components/issues/issue-detail";
// helpers
import { getDescriptionPlaceholder } from "@/helpers/issue.helper";
// hooks
import { useWorkspace } from "@/hooks/store";
-import { useIssueDescription } from "@/hooks/use-issue-description";
// services
import { FileService } from "@/services/file.service";
const fileService = new FileService();
export type IssueDescriptionInputProps = {
containerClassName?: string;
- descriptionBinary: string | null;
- descriptionHTML: string;
- disabled?: boolean;
+ workspaceSlug: string;
+ projectId: string;
issueId: string;
- key: string;
+ initialValue: string | undefined;
+ disabled?: boolean;
+ issueOperations: TIssueOperations;
placeholder?: string | ((isFocused: boolean, value: string) => string);
- projectId: string;
setIsSubmitting: (initialValue: "submitting" | "submitted" | "saved") => void;
- updateDescription: (data: string) => Promise;
- workspaceSlug: string;
+ swrIssueDescription?: string | null | undefined;
};
export const IssueDescriptionInput: FC = observer((props) => {
const {
containerClassName,
- descriptionBinary: savedDescriptionBinary,
- descriptionHTML,
- disabled,
- issueId,
- placeholder,
+ workspaceSlug,
projectId,
+ issueId,
+ disabled,
+ swrIssueDescription,
+ initialValue,
+ issueOperations,
setIsSubmitting,
- updateDescription,
- workspaceSlug,
+ placeholder,
} = props;
- // refs
- const editorRef = useRef(null);
- // store hooks
- const { getWorkspaceBySlug } = useWorkspace();
- // derived values
- const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id?.toString() ?? "";
- // use issue description
- const { descriptionBinary, resolveConflictsAndUpdateDescription } = useIssueDescription({
- descriptionBinary: savedDescriptionBinary,
- descriptionHTML,
- updateDescription,
+
+ const { handleSubmit, reset, control } = useForm({
+ defaultValues: {
+ description_html: initialValue,
+ },
});
- const debouncedDescriptionSave = useCallback(
- debounce(async (updatedDescription: Uint8Array) => {
- const editor = editorRef.current;
- if (!editor) return;
- const encodedDescription = convertBinaryDataToBase64String(updatedDescription);
- await resolveConflictsAndUpdateDescription(encodedDescription, editor);
- setIsSubmitting("submitted");
- }, 1500),
- []
+ const [localIssueDescription, setLocalIssueDescription] = useState({
+ id: issueId,
+ description_html: initialValue,
+ });
+
+ const handleDescriptionFormSubmit = useCallback(
+ async (formData: Partial) => {
+ await issueOperations.update(workspaceSlug, projectId, issueId, {
+ description_html: formData.description_html ?? "",
+ });
+ },
+ [workspaceSlug, projectId, issueId, issueOperations]
);
- if (!descriptionBinary)
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
+ const { getWorkspaceBySlug } = useWorkspace();
+ // computed values
+ const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id as string;
+
+ // reset form values
+ useEffect(() => {
+ if (!issueId) return;
+ reset({
+ id: issueId,
+ description_html: initialValue === "" ? "" : initialValue,
+ });
+ setLocalIssueDescription({
+ id: issueId,
+ description_html: initialValue === "" ? "" : initialValue,
+ });
+ }, [initialValue, issueId, reset]);
+
+ // ADDING handleDescriptionFormSubmit TO DEPENDENCY ARRAY PRODUCES ADVERSE EFFECTS
+ // TODO: Verify the exhaustive-deps warning
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const debouncedFormSave = useCallback(
+ debounce(async () => {
+ handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
+ }, 1500),
+ [handleSubmit, issueId]
+ );
return (
<>
- {!disabled ? (
- {
- setIsSubmitting("submitting");
- debouncedDescriptionSave(val);
- }}
- dragDropEnabled
- id={issueId}
- placeholder={placeholder ? placeholder : (isFocused, value) => getDescriptionPlaceholder(isFocused, value)}
- projectId={projectId}
- ref={editorRef}
- uploadFile={async (file) => {
- try {
- const { asset_id } = await fileService.uploadProjectAsset(
- workspaceSlug,
- projectId,
- {
- entity_identifier: issueId,
- entity_type: EFileAssetType.ISSUE_DESCRIPTION,
- },
- file
- );
- return asset_id;
- } catch (error) {
- console.log("Error in uploading issue asset:", error);
- throw new Error("Asset upload failed. Please try again later.");
- }
- }}
- workspaceId={workspaceId}
- workspaceSlug={workspaceSlug}
+ {localIssueDescription.description_html ? (
+
+ !disabled ? (
+ "}
+ value={swrIssueDescription ?? null}
+ workspaceSlug={workspaceSlug}
+ workspaceId={workspaceId}
+ projectId={projectId}
+ dragDropEnabled
+ onChange={(_description: object, description_html: string) => {
+ setIsSubmitting("submitting");
+ onChange(description_html);
+ debouncedFormSave();
+ }}
+ placeholder={
+ placeholder ? placeholder : (isFocused, value) => getDescriptionPlaceholder(isFocused, value)
+ }
+ containerClassName={containerClassName}
+ uploadFile={async (file) => {
+ try {
+ const { asset_id } = await fileService.uploadProjectAsset(
+ workspaceSlug,
+ projectId,
+ {
+ entity_identifier: issueId,
+ entity_type: EFileAssetType.ISSUE_DESCRIPTION,
+ },
+ file
+ );
+ return asset_id;
+ } catch (error) {
+ console.log("Error in uploading issue asset:", error);
+ throw new Error("Asset upload failed. Please try again later.");
+ }
+ }}
+ />
+ ) : (
+
+ )
+ }
/>
) : (
-
+
+
+
)}
>
);
diff --git a/web/core/components/issues/issue-detail/main-content.tsx b/web/core/components/issues/issue-detail/main-content.tsx
index 37be0c6c6dc..fb4dbc1fce9 100644
--- a/web/core/components/issues/issue-detail/main-content.tsx
+++ b/web/core/components/issues/issue-detail/main-content.tsx
@@ -22,7 +22,6 @@ import useSize from "@/hooks/use-window-size";
// plane web components
import { DeDupeIssuePopoverRoot } from "@/plane-web/components/de-dupe";
import { IssueTypeSwitcher } from "@/plane-web/components/issues";
-// plane web hooks
import { useDebouncedDuplicateIssues } from "@/plane-web/hooks/use-debounced-duplicate-issues";
// types
import { TIssueOperations } from "./root";
@@ -114,22 +113,16 @@ export const IssueMainContent: React.FC = observer((props) => {
containerClassName="-ml-3"
/>
- {issue.description_binary !== undefined && (
- "}
- disabled={!isEditable}
- updateDescription={async (data) =>
- await issueOperations.updateDescription(workspaceSlug, issue.project_id ?? "", issue.id, data)
- }
- issueId={issue.id}
- projectId={issue.project_id}
- setIsSubmitting={(value) => setIsSubmitting(value)}
- workspaceSlug={workspaceSlug}
- />
- )}
+ setIsSubmitting(value)}
+ containerClassName="-ml-3 border-none"
+ />
{currentUser && (
Promise;
update: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise;
- updateDescription: (
- workspaceSlug: string,
- projectId: string,
- issueId: string,
- descriptionBinary: string
- ) => Promise;
remove: (workspaceSlug: string, projectId: string, issueId: string) => Promise;
archive?: (workspaceSlug: string, projectId: string, issueId: string) => Promise;
restore?: (workspaceSlug: string, projectId: string, issueId: string) => Promise;
@@ -70,7 +64,6 @@ export const IssueDetailRoot: FC = observer((props) => {
issue: { getIssueById },
fetchIssue,
updateIssue,
- updateIssueDescription,
removeIssue,
archiveIssue,
addCycleToIssue,
@@ -125,13 +118,6 @@ export const IssueDetailRoot: FC = observer((props) => {
});
}
},
- updateDescription: async (workspaceSlug, projectId, issueId, descriptionBinary) => {
- try {
- return await updateIssueDescription(workspaceSlug, projectId, issueId, descriptionBinary);
- } catch {
- throw new Error("Failed to update issue description");
- }
- },
remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
if (is_archived) await removeArchivedIssue(workspaceSlug, projectId, issueId);
@@ -331,7 +317,6 @@ export const IssueDetailRoot: FC = observer((props) => {
is_archived,
fetchIssue,
updateIssue,
- updateIssueDescription,
removeIssue,
archiveIssue,
removeArchivedIssue,
diff --git a/web/core/components/issues/peek-overview/issue-detail.tsx b/web/core/components/issues/peek-overview/issue-detail.tsx
index 6aec30422bb..74aba71fdc0 100644
--- a/web/core/components/issues/peek-overview/issue-detail.tsx
+++ b/web/core/components/issues/peek-overview/issue-detail.tsx
@@ -12,9 +12,8 @@ import useReloadConfirmations from "@/hooks/use-reload-confirmation";
// plane web components
import { DeDupeIssuePopoverRoot } from "@/plane-web/components/de-dupe";
import { IssueTypeSwitcher } from "@/plane-web/components/issues";
-// plane web hooks
-import { useDebouncedDuplicateIssues } from "@/plane-web/hooks/use-debounced-duplicate-issues";
// local components
+import { useDebouncedDuplicateIssues } from "@/plane-web/hooks/use-debounced-duplicate-issues";
import { IssueDescriptionInput } from "../description-input";
import { IssueReaction } from "../issue-detail/reactions";
import { IssueTitleInput } from "../title-input";
@@ -64,6 +63,13 @@ export const PeekOverviewIssueDetails: FC = observer(
if (!issue || !issue.project_id) return <>>;
+ const issueDescription =
+ issue.description_html !== undefined || issue.description_html !== null
+ ? issue.description_html != ""
+ ? issue.description_html
+ : ""
+ : undefined;
+
return (
{issue.parent_id && (
@@ -99,22 +105,16 @@ export const PeekOverviewIssueDetails: FC
= observer(
containerClassName="-ml-3"
/>
- {issue.description_binary !== undefined && (
- "}
- disabled={disabled}
- updateDescription={async (data) =>
- await issueOperations.updateDescription(workspaceSlug, issue.project_id ?? "", issue.id, data)
- }
- issueId={issue.id}
- projectId={issue.project_id}
- setIsSubmitting={(value) => setIsSubmitting(value)}
- workspaceSlug={workspaceSlug}
- />
- )}
+ setIsSubmitting(value)}
+ containerClassName="-ml-3 border-none"
+ />
{currentUser && (
= observer((props) => {
setPeekIssue,
issue: { fetchIssue, getIsFetchingIssueDetails },
fetchActivities,
- updateIssueDescription,
} = useIssueDetail();
const { issues } = useIssuesStore();
@@ -93,16 +92,6 @@ export const IssuePeekOverview: FC = observer((props) => {
});
}
},
- updateDescription: async (workspaceSlug, projectId, issueId, descriptionBinary) => {
- if (!workspaceSlug || !projectId || !issueId) {
- throw new Error("Required fields missing while updating binary description");
- }
- try {
- return await updateIssueDescription(workspaceSlug, projectId, issueId, descriptionBinary);
- } catch {
- throw new Error("Failed to update issue description");
- }
- },
remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
return issues?.removeIssue(workspaceSlug, projectId, issueId).then(() => {
@@ -329,17 +318,7 @@ export const IssuePeekOverview: FC = observer((props) => {
}
},
}),
- [
- fetchIssue,
- is_draft,
- issues,
- fetchActivities,
- captureIssueEvent,
- pathname,
- removeRoutePeekId,
- restoreIssue,
- updateIssueDescription,
- ]
+ [fetchIssue, is_draft, issues, fetchActivities, captureIssueEvent, pathname, removeRoutePeekId, restoreIssue]
);
useEffect(() => {
diff --git a/web/core/components/pages/editor/page-root.tsx b/web/core/components/pages/editor/page-root.tsx
index 500a77586f9..ff1f3519e93 100644
--- a/web/core/components/pages/editor/page-root.tsx
+++ b/web/core/components/pages/editor/page-root.tsx
@@ -50,9 +50,7 @@ export const PageRoot = observer((props: TPageRootProps) => {
usePageFallback({
editorRef,
fetchPageDescription: async () => {
- if (!page.id) {
- throw new Error("Required fields missing while fetching binary description");
- }
+ if (!page.id) return;
return await projectPageService.fetchDescriptionBinary(workspaceSlug, projectId, page.id);
},
hasConnectionFailed,
diff --git a/web/core/components/profile/activity/activity-list.tsx b/web/core/components/profile/activity/activity-list.tsx
index 6b83a92bbde..bdb6c6f9356 100644
--- a/web/core/components/profile/activity/activity-list.tsx
+++ b/web/core/components/profile/activity/activity-list.tsx
@@ -8,7 +8,7 @@ import { IUserActivityResponse } from "@plane/types";
// components
import { ActivityIcon, ActivityMessage, IssueLink } from "@/components/core";
// editor
-import { RichTextReadOnlyEditor } from "@/components/editor";
+import { RichTextReadOnlyEditor } from "@/components/editor/rich-text-editor/rich-text-read-only-editor";
// ui
import { ActivitySettingsLoader } from "@/components/ui";
// helpers
diff --git a/web/core/components/profile/activity/profile-activity-list.tsx b/web/core/components/profile/activity/profile-activity-list.tsx
index 0fe9b44f9a8..6878fe9b3c7 100644
--- a/web/core/components/profile/activity/profile-activity-list.tsx
+++ b/web/core/components/profile/activity/profile-activity-list.tsx
@@ -7,7 +7,7 @@ import useSWR from "swr";
import { History, MessageSquare } from "lucide-react";
// hooks
import { ActivityIcon, ActivityMessage, IssueLink } from "@/components/core";
-import { RichTextReadOnlyEditor } from "@/components/editor";
+import { RichTextReadOnlyEditor } from "@/components/editor/rich-text-editor/rich-text-read-only-editor";
import { ActivitySettingsLoader } from "@/components/ui";
// constants
import { USER_ACTIVITY } from "@/constants/fetch-keys";
diff --git a/web/core/hooks/use-issue-description.ts b/web/core/hooks/use-issue-description.ts
deleted file mode 100644
index 5493ffae02d..00000000000
--- a/web/core/hooks/use-issue-description.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { useCallback, useEffect, useState } from "react";
-// plane editor
-import {
- convertBase64StringToBinaryData,
- EditorRefApi,
- getBinaryDataFromRichTextEditorHTMLString,
-} from "@plane/editor";
-
-type TArgs = {
- descriptionBinary: string | null;
- descriptionHTML: string | null;
- updateDescription?: (data: string) => Promise;
-};
-
-export const useIssueDescription = (args: TArgs) => {
- const { descriptionBinary: savedDescriptionBinary, descriptionHTML, updateDescription } = args;
- // states
- const [descriptionBinary, setDescriptionBinary] = useState(null);
- // update description
- const resolveConflictsAndUpdateDescription = useCallback(
- async (encodedDescription: string, editorRef: EditorRefApi | null) => {
- if (!updateDescription) return;
- try {
- const conflictFreeEncodedDescription = await updateDescription(encodedDescription);
- const decodedDescription = conflictFreeEncodedDescription
- ? new Uint8Array(conflictFreeEncodedDescription)
- : new Uint8Array();
- editorRef?.setProviderDocument(decodedDescription);
- } catch (error) {
- console.error("Error while updating description", error);
- }
- },
- [updateDescription]
- );
-
- useEffect(() => {
- if (descriptionBinary) return;
- if (savedDescriptionBinary) {
- const savedDescriptionBuffer = convertBase64StringToBinaryData(savedDescriptionBinary);
- const decodedSavedDescription = savedDescriptionBuffer
- ? new Uint8Array(savedDescriptionBuffer)
- : new Uint8Array();
- setDescriptionBinary(decodedSavedDescription);
- } else {
- const decodedDescriptionHTML = getBinaryDataFromRichTextEditorHTMLString(descriptionHTML ?? "");
- setDescriptionBinary(decodedDescriptionHTML);
- }
- }, [descriptionBinary, descriptionHTML, savedDescriptionBinary]);
-
- return {
- descriptionBinary,
- resolveConflictsAndUpdateDescription,
- };
-};
diff --git a/web/core/hooks/use-page-fallback.ts b/web/core/hooks/use-page-fallback.ts
index 4e604ffb466..9f5ef348293 100644
--- a/web/core/hooks/use-page-fallback.ts
+++ b/web/core/hooks/use-page-fallback.ts
@@ -1,6 +1,6 @@
import { useCallback, useEffect } from "react";
// plane editor
-import { convertBinaryDataToBase64String, EditorRefApi } from "@plane/editor";
+import { EditorRefApi } from "@plane/editor";
// plane types
import { TDocumentPayload } from "@plane/types";
// hooks
@@ -8,7 +8,7 @@ import useAutoSave from "@/hooks/use-auto-save";
type TArgs = {
editorRef: React.RefObject;
- fetchPageDescription: () => Promise;
+ fetchPageDescription: () => Promise;
hasConnectionFailed: boolean;
updatePageDescription: (data: TDocumentPayload) => Promise;
};
@@ -29,7 +29,7 @@ export const usePageFallback = (args: TArgs) => {
editor.setProviderDocument(latestDecodedDescription);
const { binary, html, json } = editor.getDocument();
if (!binary || !json) return;
- const encodedBinary = convertBinaryDataToBase64String(binary);
+ const encodedBinary = Buffer.from(binary).toString("base64");
await updatePageDescription({
description_binary: encodedBinary,
diff --git a/web/core/services/inbox/inbox-issue.service.ts b/web/core/services/inbox/inbox-issue.service.ts
index d8e6357cc9c..8837b6e74a3 100644
--- a/web/core/services/inbox/inbox-issue.service.ts
+++ b/web/core/services/inbox/inbox-issue.service.ts
@@ -1,5 +1,5 @@
// types
-import type { TInboxIssue, TIssue, TInboxIssueWithPagination, TInboxForm, TDocumentPayload } from "@plane/types";
+import type { TInboxIssue, TIssue, TInboxIssueWithPagination, TInboxForm } from "@plane/types";
import { API_BASE_URL } from "@/helpers/common.helper";
import { APIService } from "@/services/api.service";
// helpers
@@ -76,25 +76,6 @@ export class InboxIssueService extends APIService {
});
}
- async updateDescriptionBinary(
- workspaceSlug: string,
- projectId: string,
- inboxIssueId: string,
- data: Pick
- ): Promise {
- return this.post(
- `/api/workspaces/${workspaceSlug}/projects/${projectId}/inbox-issues/${inboxIssueId}/description/`,
- data,
- {
- responseType: "arraybuffer",
- }
- )
- .then((response) => response?.data)
- .catch((error) => {
- throw error?.response?.data;
- });
- }
-
async retrievePublishForm(workspaceSlug: string, projectId: string): Promise {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/intake-settings/`)
.then((response) => response?.data)
diff --git a/web/core/services/issue/issue.service.ts b/web/core/services/issue/issue.service.ts
index 5a9854062f9..2cef113d80f 100644
--- a/web/core/services/issue/issue.service.ts
+++ b/web/core/services/issue/issue.service.ts
@@ -4,7 +4,6 @@ import isEmpty from "lodash/isEmpty";
import type {
IIssueDisplayProperties,
TBulkOperationsPayload,
- TDocumentPayload,
TIssue,
TIssueActivity,
TIssueLink,
@@ -389,19 +388,4 @@ export class IssueService extends APIService {
throw error?.response?.data;
});
}
-
- async updateDescriptionBinary(
- workspaceSlug: string,
- projectId: string,
- issueId: string,
- data: Pick
- ): Promise {
- return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/description/`, data, {
- responseType: "arraybuffer",
- })
- .then((response) => response?.data)
- .catch((error) => {
- throw error?.response?.data;
- });
- }
}
diff --git a/web/core/services/page/project-page.service.ts b/web/core/services/page/project-page.service.ts
index f5331d8891f..00d9401a69a 100644
--- a/web/core/services/page/project-page.service.ts
+++ b/web/core/services/page/project-page.service.ts
@@ -4,10 +4,15 @@ import { TDocumentPayload, TPage } from "@plane/types";
import { API_BASE_URL } from "@/helpers/common.helper";
// services
import { APIService } from "@/services/api.service";
+import { FileUploadService } from "@/services/file-upload.service";
export class ProjectPageService extends APIService {
+ private fileUploadService: FileUploadService;
+
constructor() {
super(API_BASE_URL);
+ // upload service
+ this.fileUploadService = new FileUploadService();
}
async fetchAll(workspaceSlug: string, projectId: string): Promise {
@@ -128,7 +133,7 @@ export class ProjectPageService extends APIService {
});
}
- async fetchDescriptionBinary(workspaceSlug: string, projectId: string, pageId: string): Promise {
+ async fetchDescriptionBinary(workspaceSlug: string, projectId: string, pageId: string): Promise {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/description/`, {
headers: {
"Content-Type": "application/octet-stream",
diff --git a/web/core/store/inbox/inbox-issue.store.ts b/web/core/store/inbox/inbox-issue.store.ts
index 30a03207d8d..e080225aaf2 100644
--- a/web/core/store/inbox/inbox-issue.store.ts
+++ b/web/core/store/inbox/inbox-issue.store.ts
@@ -26,7 +26,6 @@ export interface IInboxIssueStore {
updateInboxIssueDuplicateTo: (issueId: string) => Promise; // connecting the inbox issue to the project existing issue
updateInboxIssueSnoozeTill: (date: Date | undefined) => Promise; // snooze the issue
updateIssue: (issue: Partial) => Promise; // updating the issue
- updateIssueDescription: (descriptionBinary: string) => Promise; // updating the local issue description
updateProjectIssue: (issue: Partial) => Promise; // updating the issue
fetchIssueActivity: () => Promise; // fetching the issue activity
}
@@ -79,7 +78,6 @@ export class InboxIssueStore implements IInboxIssueStore {
updateInboxIssueDuplicateTo: action,
updateInboxIssueSnoozeTill: action,
updateIssue: action,
- updateIssueDescription: action,
updateProjectIssue: action,
fetchIssueActivity: action,
});
@@ -177,26 +175,6 @@ export class InboxIssueStore implements IInboxIssueStore {
}
};
- updateIssueDescription = async (descriptionBinary: string): Promise => {
- try {
- if (!this.issue.id) throw new Error("Issue id is missing");
- const res = await this.inboxIssueService.updateDescriptionBinary(
- this.workspaceSlug,
- this.projectId,
- this.issue.id,
- {
- description_binary: descriptionBinary,
- }
- );
- set(this.issue, "description_binary", descriptionBinary);
- // fetching activity
- this.fetchIssueActivity();
- return res;
- } catch {
- throw new Error("Failed to update local issue description");
- }
- };
-
updateProjectIssue = async (issue: Partial) => {
const inboxIssue = clone(this.issue);
try {
diff --git a/web/core/store/issue/issue-details/issue.store.ts b/web/core/store/issue/issue-details/issue.store.ts
index 342c3adca67..db0ccc39af2 100644
--- a/web/core/store/issue/issue-details/issue.store.ts
+++ b/web/core/store/issue/issue-details/issue.store.ts
@@ -7,7 +7,6 @@ import { persistence } from "@/local-db/storage.sqlite";
// services
import { IssueArchiveService, IssueDraftService, IssueService } from "@/services/issue";
// types
-import { IIssueRootStore } from "../root.store";
import { IIssueDetail } from "./root.store";
export interface IIssueStoreActions {
@@ -16,15 +15,9 @@ export interface IIssueStoreActions {
workspaceSlug: string,
projectId: string,
issueId: string,
- issueStatus?: "DEFAULT" | "DRAFT"
+ issueStatus?: "DEFAULT" | "DRAFT",
) => Promise;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise;
- updateIssueDescription: (
- workspaceSlug: string,
- projectId: string,
- issueId: string,
- descriptionBinary: string
- ) => Promise;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise;
addCycleToIssue: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise;
@@ -51,21 +44,19 @@ export class IssueStore implements IIssueStore {
fetchingIssueDetails: string | undefined = undefined;
localDBIssueDescription: string | undefined = undefined;
// root store
- rootIssueStore: IIssueRootStore;
rootIssueDetailStore: IIssueDetail;
// services
issueService;
issueArchiveService;
issueDraftService;
- constructor(rootStore: IIssueRootStore, rootIssueDetailStore: IIssueDetail) {
+ constructor(rootStore: IIssueDetail) {
makeObservable(this, {
fetchingIssueDetails: observable.ref,
localDBIssueDescription: observable.ref,
});
// root store
- this.rootIssueStore = rootStore;
- this.rootIssueDetailStore = rootIssueDetailStore;
+ this.rootIssueDetailStore = rootStore;
// services
this.issueService = new IssueService();
this.issueArchiveService = new IssueArchiveService();
@@ -165,7 +156,6 @@ export class IssueStore implements IIssueStore {
id: issue?.id,
sequence_id: issue?.sequence_id,
name: issue?.name,
- description_binary: issue?.description_binary,
description_html: issue?.description_html,
sort_order: issue?.sort_order,
state_id: issue?.state_id,
@@ -204,20 +194,6 @@ export class IssueStore implements IIssueStore {
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
};
- updateIssueDescription = async (
- workspaceSlug: string,
- projectId: string,
- issueId: string,
- descriptionBinary: string
- ): Promise => {
- const res = await this.issueService.updateDescriptionBinary(workspaceSlug, projectId, issueId, {
- description_binary: descriptionBinary,
- });
- this.rootIssueStore.issues.updateIssue(issueId, { description_binary: descriptionBinary });
- this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
- return res;
- };
-
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
this.rootIssueDetailStore.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId);
diff --git a/web/core/store/issue/issue-details/root.store.ts b/web/core/store/issue/issue-details/root.store.ts
index 6a98715d9b4..e6e0ca8d0b2 100644
--- a/web/core/store/issue/issue-details/root.store.ts
+++ b/web/core/store/issue/issue-details/root.store.ts
@@ -192,7 +192,7 @@ export class IssueDetail implements IIssueDetail {
// store
this.rootIssueStore = rootStore;
- this.issue = new IssueStore(rootStore, this);
+ this.issue = new IssueStore(this);
this.reaction = new IssueReactionStore(this);
this.attachment = new IssueAttachmentStore(rootStore);
this.activity = new IssueActivityStore(rootStore.rootStore as RootStore);
@@ -257,12 +257,6 @@ export class IssueDetail implements IIssueDetail {
) => this.issue.fetchIssue(workspaceSlug, projectId, issueId, issueStatus);
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) =>
this.issue.updateIssue(workspaceSlug, projectId, issueId, data);
- updateIssueDescription = async (
- workspaceSlug: string,
- projectId: string,
- issueId: string,
- descriptionBinary: string
- ) => this.issue.updateIssueDescription(workspaceSlug, projectId, issueId, descriptionBinary);
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
this.issue.removeIssue(workspaceSlug, projectId, issueId);
archiveIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>