Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-1322] dev: conflict free pages collaboration #4463

Merged
merged 44 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c009a45
chore: pages realtime
NarayanBavisetti May 8, 2024
a9b45ed
fix: merge conflicts resolved from preview
aaryan610 May 8, 2024
7dada8a
chore: empty binary response
NarayanBavisetti May 8, 2024
16fc909
Merge branch 'chore/pages-yjs' of github.com:makeplane/plane into cho…
NarayanBavisetti May 8, 2024
f277aa0
Merge branch 'chore/pages-yjs' of https://github.com/makeplane/plane …
aaryan610 May 9, 2024
25868bf
Merge branch 'chore/pages-yjs' of github.com:makeplane/plane into fea…
NarayanBavisetti May 10, 2024
4ccdd29
chore: added a ypy package
NarayanBavisetti May 10, 2024
6f190ea
Merge branch 'feat/pages-collaboration' of https://github.com/makepla…
aaryan610 May 10, 2024
17c5396
feat: pages collaboration
aaryan610 May 11, 2024
5c87e35
chore: update fetching logic
aaryan610 May 11, 2024
01b5fed
chore: degrade ypy version
NarayanBavisetti May 11, 2024
209df5b
chore: replace useEffect fetch logic with useSWR
aaryan610 May 12, 2024
b736bbb
Merge branch 'feat-pages-collaboration' of https://github.com/makepla…
aaryan610 May 12, 2024
52fddae
chore: move all the update logic to the page store
aaryan610 May 12, 2024
d131939
refactor: remove react-hook-form
aaryan610 May 12, 2024
f4faa1e
chore: save description_html as well
aaryan610 May 14, 2024
b4ccc72
chore: migrate old data logic
aaryan610 May 15, 2024
70f2164
fix: merge conflicts resolved from preview
aaryan610 May 15, 2024
fde1c3f
fix: added description_binary as field name
NarayanBavisetti May 15, 2024
10d118a
fix: code cleanup
NarayanBavisetti May 15, 2024
8c46a54
Merge branch 'preview' of https://github.com/makeplane/plane into fea…
aaryan610 May 15, 2024
4579a94
refactor: create separate hook to handle page description
aaryan610 May 15, 2024
006f4d5
Merge branch 'feat-pages-collaboration' of https://github.com/makepla…
aaryan610 May 15, 2024
28fc3f3
fix: build errors
aaryan610 May 15, 2024
6f6e99d
chore: combine updates instead of using the whole document
aaryan610 May 15, 2024
33776de
fix: merge conflicts resolved from preview
aaryan610 May 17, 2024
42c6ba4
chore: removed ypy package
NarayanBavisetti May 17, 2024
332b677
chore: added conflict resolving logic to the client side
aaryan610 May 17, 2024
faeafea
chore: add a save changes button
aaryan610 May 17, 2024
65a7159
chore: add read-only validation
aaryan610 May 17, 2024
8b6844f
chore: remove saving state information
aaryan610 May 17, 2024
fbd799f
chore: added permission class
NarayanBavisetti May 17, 2024
cfcbd49
Merge branch 'feat-pages-collaboration' of github.com:makeplane/plane…
NarayanBavisetti May 17, 2024
1564e58
Merge branch 'preview' of github.com:makeplane/plane into feat-pages-…
NarayanBavisetti May 17, 2024
a70deb3
chore: removed the migration file
NarayanBavisetti May 17, 2024
94594b3
chore: corrected the model field
NarayanBavisetti May 17, 2024
503d3ba
chore: rename pageStore to page
aaryan610 May 17, 2024
10aa8d3
Merge branch 'feat-pages-collaboration' of https://github.com/makepla…
aaryan610 May 17, 2024
fd7e336
Merge branch 'preview' of https://github.com/makeplane/plane into fea…
aaryan610 May 19, 2024
c36a60c
Merge branch 'preview' of https://github.com/makeplane/plane into fea…
aaryan610 May 20, 2024
7a89c9a
chore: update collaboration provider
aaryan610 May 20, 2024
3fef19c
Merge branch 'preview' of https://github.com/makeplane/plane into fea…
aaryan610 May 22, 2024
f893629
chore: add try catch to handle error
aaryan610 May 22, 2024
0fc84b1
fix: merge conflicts resolved from preview
aaryan610 May 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apiserver/plane/app/serializers/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ class PageDetailSerializer(PageSerializer):
description_html = serializers.CharField()

class Meta(PageSerializer.Meta):
fields = PageSerializer.Meta.fields + ["description_html"]
fields = PageSerializer.Meta.fields + [
"description_html",
]


class SubPageSerializer(BaseSerializer):
Expand Down
11 changes: 11 additions & 0 deletions apiserver/plane/app/urls/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
PageFavoriteViewSet,
PageLogEndpoint,
SubPagesEndpoint,
PagesDescriptionViewSet,
)


Expand Down Expand Up @@ -79,4 +80,14 @@
SubPagesEndpoint.as_view(),
name="sub-page",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/description/",
PagesDescriptionViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
}
),
name="page-description",
),
]
1 change: 1 addition & 0 deletions apiserver/plane/app/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
PageFavoriteViewSet,
PageLogEndpoint,
SubPagesEndpoint,
PagesDescriptionViewSet,
)

from .search import GlobalSearchEndpoint, IssueSearchEndpoint
Expand Down
47 changes: 47 additions & 0 deletions apiserver/plane/app/views/page/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Python imports
import json
import base64
from datetime import datetime
from django.core.serializers.json import DjangoJSONEncoder

Expand All @@ -8,6 +9,7 @@
from django.db.models import Exists, OuterRef, Q
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
Expand Down Expand Up @@ -388,3 +390,48 @@ def get(self, request, slug, project_id, page_id):
return Response(
SubPageSerializer(pages, many=True).data, status=status.HTTP_200_OK
)


class PagesDescriptionViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]

def retrieve(self, request, slug, project_id, pk):
page = Page.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id
)
binary_data = page.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="page_description.bin"'
)
return response

def partial_update(self, request, slug, project_id, pk):
page = Page.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id
)

base64_data = request.data.get("description_binary")

if base64_data:
# Decode the base64 data to bytes
new_binary_data = base64.b64decode(base64_data)

# Store the updated binary data
page.description_binary = new_binary_data
page.description_html = request.data.get("description_html")
page.save()
return Response({"message": "Updated successfully"})
else:
return Response({"error": "No binary data provided"})
2 changes: 1 addition & 1 deletion apiserver/plane/db/models/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def get_view_props():
class Page(ProjectBaseModel):
name = models.CharField(max_length=255, blank=True)
description = models.JSONField(default=dict, blank=True)
description_binary = models.BinaryField(null=True)
description_html = models.TextField(blank=True, default="<p></p>")
description_stripped = models.TextField(blank=True, null=True)
owned_by = models.ForeignKey(
Expand All @@ -43,7 +44,6 @@ class Page(ProjectBaseModel):
is_locked = models.BooleanField(default=False)
view_props = models.JSONField(default=get_view_props)
logo_props = models.JSONField(default=dict)
description_binary = models.BinaryField(null=True)

class Meta:
verbose_name = "Page"
Expand Down
41 changes: 23 additions & 18 deletions packages/editor/core/src/hooks/use-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ import { EditorMenuItemNames, getEditorMenuItems } from "src/ui/menus/menu-items
import { EditorRefApi } from "src/types/editor-ref-api";
import { IMarking, scrollSummary } from "src/helpers/scroll-to-node";

interface CustomEditorProps {
export type TFileHandler = {
cancel: () => void;
delete: DeleteImage;
upload: UploadImage;
restore: RestoreImage;
};

export interface CustomEditorProps {
id?: string;
uploadFile: UploadImage;
restoreFile: RestoreImage;
deleteFile: DeleteImage;
cancelUploadImage?: () => void;
initialValue: string;
fileHandler: TFileHandler;
initialValue?: string;
editorClassName: string;
// undefined when prop is not passed, null if intentionally passed to stop
// swr syncing
value: string | null | undefined;
value?: string | null | undefined;
onChange?: (json: object, html: string) => void;
extensions?: any;
editorProps?: EditorProps;
Expand All @@ -38,19 +42,16 @@ interface CustomEditorProps {
}

export const useEditor = ({
uploadFile,
id = "",
deleteFile,
cancelUploadImage,
editorProps = {},
initialValue,
editorClassName,
value,
extensions = [],
fileHandler,
onChange,
forwardedRef,
tabIndex,
restoreFile,
handleEditorReady,
mentionHandler,
placeholder,
Expand All @@ -67,10 +68,10 @@ export const useEditor = ({
mentionHighlights: mentionHandler.highlights ?? [],
},
fileConfig: {
deleteFile,
restoreFile,
cancelUploadImage,
uploadFile,
uploadFile: fileHandler.upload,
deleteFile: fileHandler.delete,
restoreFile: fileHandler.restore,
cancelUploadImage: fileHandler.cancel,
},
placeholder,
tabIndex,
Expand Down Expand Up @@ -139,7 +140,7 @@ export const useEditor = ({
}
},
executeMenuItemCommand: (itemName: EditorMenuItemNames) => {
const editorItems = getEditorMenuItems(editorRef.current, uploadFile);
const editorItems = getEditorMenuItems(editorRef.current, fileHandler.upload);

const getEditorMenuItem = (itemName: EditorMenuItemNames) => editorItems.find((item) => item.key === itemName);

Expand All @@ -155,7 +156,7 @@ export const useEditor = ({
}
},
isMenuItemActive: (itemName: EditorMenuItemNames): boolean => {
const editorItems = getEditorMenuItems(editorRef.current, uploadFile);
const editorItems = getEditorMenuItems(editorRef.current, fileHandler.upload);

const getEditorMenuItem = (itemName: EditorMenuItemNames) => editorItems.find((item) => item.key === itemName);
const item = getEditorMenuItem(itemName);
Expand All @@ -177,6 +178,10 @@ export const useEditor = ({
const markdownOutput = editorRef.current?.storage.markdown.getMarkdown();
return markdownOutput;
},
getHTML: (): string => {
const htmlOutput = editorRef.current?.getHTML() ?? "<p></p>";
return htmlOutput;
},
scrollSummary: (marking: IMarking): void => {
if (!editorRef.current) return;
scrollSummary(editorRef.current, marking);
Expand All @@ -199,7 +204,7 @@ export const useEditor = ({
}
},
}),
[editorRef, savedSelection, uploadFile]
[editorRef, savedSelection, fileHandler.upload]
);

if (!editor) {
Expand Down
4 changes: 4 additions & 0 deletions packages/editor/core/src/hooks/use-read-only-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export const useReadOnlyEditor = ({
const markdownOutput = editorRef.current?.storage.markdown.getMarkdown();
return markdownOutput;
},
getHTML: (): string => {
const htmlOutput = editorRef.current?.getHTML() ?? "<p></p>";
return htmlOutput;
},
scrollSummary: (marking: IMarking): void => {
if (!editorRef.current) return;
scrollSummary(editorRef.current, marking);
Expand Down
1 change: 1 addition & 0 deletions packages/editor/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export * from "src/ui/menus/menu-items";
export * from "src/lib/editor-commands";

// types
export type { CustomEditorProps, TFileHandler } from "src/hooks/use-editor";
export type { DeleteImage } from "src/types/delete-image";
export type { UploadImage } from "src/types/upload-image";
export type { EditorRefApi, EditorReadOnlyRefApi } from "src/types/editor-ref-api";
Expand Down
19 changes: 19 additions & 0 deletions packages/editor/core/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Extensions, generateJSON, getSchema } from "@tiptap/core";
import { Selection } from "@tiptap/pm/state";
import { clsx, type ClassValue } from "clsx";
import { CoreEditorExtensionsWithoutProps } from "src/ui/extensions/core-without-props";
import { twMerge } from "tailwind-merge";
interface EditorClassNames {
noBorder?: boolean;
Expand Down Expand Up @@ -58,3 +60,20 @@ export const isValidHttpUrl = (string: string): boolean => {

return url.protocol === "http:" || url.protocol === "https:";
};

/**
* @description return an object with contentJSON and editorSchema
* @description contentJSON- ProseMirror JSON from HTML content
* @description editorSchema- editor schema from extensions
* @param {string} html
* @returns {object} {contentJSON, editorSchema}
*/
export const generateJSONfromHTML = (html: string) => {
const extensions = CoreEditorExtensionsWithoutProps();
const contentJSON = generateJSON(html ?? "<p></p>", extensions as Extensions);
const editorSchema = getSchema(extensions as Extensions);
return {
contentJSON,
editorSchema,
};
};
1 change: 1 addition & 0 deletions packages/editor/core/src/types/editor-ref-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { EditorMenuItemNames } from "src/ui/menus/menu-items";

export type EditorReadOnlyRefApi = {
getMarkDown: () => string;
getHTML: () => string;
clearEditor: () => void;
setEditorValue: (content: string) => void;
scrollSummary: (marking: IMarking) => void;
Expand Down
Loading
Loading