[WEB-460] refactor: editors, chore: pages list improvement (#4090)
* fix: stroing the transactions in page * fix: page details changes * chore: page response change * chore: removed duplicated endpoints * chore: optimised the urls * chore: removed archived and favorite pages * chore: revamping pages store and components * mentions loading state part done * fixed mentions not showing in modals * removed comments and cleaned up types * removed unused types * reset: head * chore: pages store and component updates * style: pages list item UI * fix: improved colors and drag handle width * fix: slash commands are no more shown in the code blocks * fix: cleanup/hide drag handles post drop * fix: hide/cleanup drag handles post drag start * fix: aligning the drag handles better with the node post css changes of the length * fix: juggling back and forth of drag handles in ordered and unordered lists * chore: fix imports, ts errors and other things * fix: clearing nodes to default node i.e paragraph before converting it to other types of nodes For more reference on what this does, please refer https://tiptap.dev/docs/editor/api/commands/clear-nodes * chore: clearNodes after delete in case of selections being present * fix: hiding link selector in the bubble menu if inline code block is selected * chore: filtering, ordering and searching implemented * chore: updated pages store and updated UI * chore: new core editor just for document editor created * chore: removed setIsSubmitting prop in doc editor * fix: fixed submitting state for image uploads * refactor: setShouldShowAlert removed * refactor: rerenderOnPropsChange prop removed * chore: type inference magic in ref to expose an api for controlling editor menu items from outside * fix: naming imports * chore: change names of the exposed functions and removing old types * refactor: remove debouncedUpdatesEnabled prop; * refactor: editor heading markings now parsed using html * chore: removed unrelated components from the document editor * refactor: page details granular components * fix: remove onActionCompleteHandler * refactor: removed rerenderOnProps change prop * feat: added getMarkDown function * chore: update dropdown option actions * fix: sidebar markings update logic * chore: add image and to-do list actions to the toolbar * fix: handling refs and populating them via callbacks * feat: scroll to node api exposed * cleaning up editor refs when the editor is destroyed * feat: scrolling added to read only instance of the editor * fix: markings logic * fix: build errors with types * fix: build erros * fix: subscribing to transactions of editor via ref * chore: remove debug statements * fix: type errors * fix: temporary different slash commands for document editor * chore: inline code extension style * chore: remove border from readOnly editor * fix: editor bottom padding * chore: pages improvements * chore: handle Enter key on the page title * feat: added loading indicator logic in mentions * fix: mentions and slash commands now work well with multiple editors in one place * refactor: page store structure, filtering logic * feat: added better seperation in inline code blocks * feat: list autojoining added * fix: pages folder structure * fix: image refocus from external parts * working lists somewhat * chore: implement page reactions * fix: build errors * fix: build errors * fixed drag handles stuff * task list item fixed * working * fix: working on multiple nested lists * chore: remove debug statements * fix: Tab key on first list item handled to not go out of editor focus * feat: threshold auto scroll support added and multi nested list selection fixed * fix: caret color bug with improved inline code blocks * fix: node range error when bulk deleting with list * fix: removed slash commands from working in code blocks * chore: update typography margins * chore: new field added in page model * fix: better type inference in slash commands * chore: code block UI * feat: image insertion at correct position using ref added * feat: added improved mentions support for space * fix: type errors in mentions for comments in web app * sync: core with document-core * fix: build errors * fix: fallback for appendTo not being able to find active container instantly * fix: page store * fix: page description * fix: css quality issues * chore: code cleanup * chore: removed placeholder text in codeblocks * chore: archived pages response change * chore: archived pages response change * fix: initial pages list fetch * fix: pages list filters and ordering * chore: add access change option in the quick actions dropdown * fix: inline code block caret fixed * regression: removing extra text * chore: caret color removed * feat: copy code button added in code blocks * fix: initial load of page details * fix: initial load of page details * fix: image resizing weird behavior on click/expanding it too much fixed now * chore: copy page response * fix: todo list spacing * chore: description html in the copy page * chore: handle latest description on refetch * fix: saner scroll behaviours * fix: block menu positioning * fix: updated empty string description * feat: tab change sync support added * fix: infinite rerendering with markings * fix: block menu finally * fix: intial load on reload bug fixed * fix: nested lists alignment * fix: editor padding * fix: first level list items copyable * chore: list spacing * fix: title change * fix: pages list block items interaction * fix: saving chip position * fix: delete action from block menu to focus properly * fix: margin-bottom as 0 to avoid weird spacing when a paragraph node follows a list node * style: table, chore: lite text editor toolbar * fix: page description tab sync * fix: lists spacing and alignment * refactor: document editor props * feat: rich text editor wrapper created and migrated core * feat: created wrapper around lite text editor and merged core * chore: add lite text editor toolbar * fix: build errors * fix: type errors and addead live updation of toolbar * chore: pages migration * fix: inbox issue * refactor: remove redundant package * refactor: unused files * fix: add dompurify to space app * fix: inline code margin * fix: editor className props * fix: build errors * fix: traversing up the tree before assuming the parent is not a list item * fix: drag handle positions for list items fixed * fix: removed focus at end logic after deleting block * fix: image wrapper overflow scroll fix with block menu's position * fix: selection and deletion logic for nested lists fixed!! * fix: hiding the block menu while scrolling in the document/app * fix: merge conflicts resolved from develop * fix: inbox issue description * chore: move page title to the web app * fix: handling edge cases for table selection * chore: lint issues * refactor: list item functions moved to same file * refactor: use mention hook * fix: added try catch blocks for mention suggestions * chore: remove unused code * fix: remove console logs * fix: remove console logs --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: gurusainath <gurusainath007@gmail.com> Co-authored-by: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com>
This commit is contained in:
parent
8b6035d315
commit
3e2355e223
248 changed files with 7602 additions and 5619 deletions
|
|
@ -93,6 +93,7 @@ from .page import (
|
|||
PageSerializer,
|
||||
PageLogSerializer,
|
||||
SubPageSerializer,
|
||||
PageDetailSerializer,
|
||||
PageFavoriteSerializer,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@ from rest_framework import serializers
|
|||
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from .issue import LabelLiteSerializer
|
||||
from .workspace import WorkspaceLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
from plane.db.models import (
|
||||
Page,
|
||||
PageLog,
|
||||
|
|
@ -17,22 +14,33 @@ from plane.db.models import (
|
|||
|
||||
class PageSerializer(BaseSerializer):
|
||||
is_favorite = serializers.BooleanField(read_only=True)
|
||||
label_details = LabelLiteSerializer(
|
||||
read_only=True, source="labels", many=True
|
||||
)
|
||||
labels = serializers.ListField(
|
||||
child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()),
|
||||
write_only=True,
|
||||
required=False,
|
||||
)
|
||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||
workspace_detail = WorkspaceLiteSerializer(
|
||||
source="workspace", read_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Page
|
||||
fields = "__all__"
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"owned_by",
|
||||
"access",
|
||||
"color",
|
||||
"labels",
|
||||
"parent",
|
||||
"is_favorite",
|
||||
"is_locked",
|
||||
"archived_at",
|
||||
"workspace",
|
||||
"project",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"view_props",
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
|
|
@ -48,8 +56,12 @@ class PageSerializer(BaseSerializer):
|
|||
labels = validated_data.pop("labels", None)
|
||||
project_id = self.context["project_id"]
|
||||
owned_by_id = self.context["owned_by_id"]
|
||||
description_html = self.context["description_html"]
|
||||
page = Page.objects.create(
|
||||
**validated_data, project_id=project_id, owned_by_id=owned_by_id
|
||||
**validated_data,
|
||||
description_html=description_html,
|
||||
project_id=project_id,
|
||||
owned_by_id=owned_by_id,
|
||||
)
|
||||
|
||||
if labels is not None:
|
||||
|
|
@ -91,6 +103,13 @@ class PageSerializer(BaseSerializer):
|
|||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class PageDetailSerializer(PageSerializer):
|
||||
description_html = serializers.CharField()
|
||||
|
||||
class Meta(PageSerializer.Meta):
|
||||
fields = PageSerializer.Meta.fields + ["description_html"]
|
||||
|
||||
|
||||
class SubPageSerializer(BaseSerializer):
|
||||
entity_details = serializers.SerializerMethodField()
|
||||
|
||||
|
|
|
|||
|
|
@ -31,102 +31,51 @@ urlpatterns = [
|
|||
),
|
||||
name="project-pages",
|
||||
),
|
||||
# favorite pages
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-pages/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/favorite-pages/<uuid:pk>/",
|
||||
PageFavoriteViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="user-favorite-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-pages/<uuid:page_id>/",
|
||||
PageFavoriteViewSet.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="user-favorite-pages",
|
||||
),
|
||||
# archived pages
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/archive/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/archive/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "archive",
|
||||
"delete": "unarchive",
|
||||
}
|
||||
),
|
||||
name="project-page-archive",
|
||||
name="project-page-archive-unarchive",
|
||||
),
|
||||
# lock and unlock
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/unarchive/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "unarchive",
|
||||
}
|
||||
),
|
||||
name="project-page-unarchive",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-pages/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"get": "archive_list",
|
||||
}
|
||||
),
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/lock/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/lock/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "lock",
|
||||
"delete": "unlock",
|
||||
}
|
||||
),
|
||||
name="project-pages",
|
||||
name="project-pages-lock-unlock",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/unlock/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "unlock",
|
||||
}
|
||||
),
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/transactions/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/transactions/",
|
||||
PageLogEndpoint.as_view(),
|
||||
name="page-transactions",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/transactions/<uuid:transaction>/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/transactions/<uuid:transaction>/",
|
||||
PageLogEndpoint.as_view(),
|
||||
name="page-transactions",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/sub-pages/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/sub-pages/",
|
||||
SubPagesEndpoint.as_view(),
|
||||
name="sub-page",
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
# Python imports
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# Django imports
|
||||
|
|
@ -17,6 +18,7 @@ from plane.app.serializers import (
|
|||
PageLogSerializer,
|
||||
PageSerializer,
|
||||
SubPageSerializer,
|
||||
PageDetailSerializer,
|
||||
)
|
||||
from plane.db.models import (
|
||||
Page,
|
||||
|
|
@ -28,6 +30,8 @@ from plane.db.models import (
|
|||
# Module imports
|
||||
from ..base import BaseAPIView, BaseViewSet
|
||||
|
||||
from plane.bgtasks.page_transaction_task import page_transaction
|
||||
|
||||
|
||||
def unarchive_archive_page_and_descendants(page_id, archived_at):
|
||||
# Your SQL query
|
||||
|
|
@ -87,11 +91,21 @@ class PageViewSet(BaseViewSet):
|
|||
def create(self, request, slug, project_id):
|
||||
serializer = PageSerializer(
|
||||
data=request.data,
|
||||
context={"project_id": project_id, "owned_by_id": request.user.id},
|
||||
context={
|
||||
"project_id": project_id,
|
||||
"owned_by_id": request.user.id,
|
||||
"description_html": request.data.get(
|
||||
"description_html", "<p></p>"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
# capture the page transaction
|
||||
page_transaction.delay(request.data, None, serializer.data["id"])
|
||||
page = Page.objects.get(pk=serializer.data["id"])
|
||||
serializer = PageDetailSerializer(page)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
|
@ -125,9 +139,22 @@ class PageViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = PageSerializer(page, data=request.data, partial=True)
|
||||
serializer = PageDetailSerializer(
|
||||
page, data=request.data, partial=True
|
||||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
# capture the page transaction
|
||||
if request.data.get("description_html"):
|
||||
page_transaction.delay(
|
||||
new_value=request.data,
|
||||
old_value=json.dumps(
|
||||
{
|
||||
"description_html": page.description_html,
|
||||
}
|
||||
),
|
||||
page_id=pk,
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
|
|
@ -140,18 +167,24 @@ class PageViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
def lock(self, request, slug, project_id, page_id):
|
||||
def retrieve(self, request, slug, project_id, pk=None):
|
||||
page = self.get_queryset().filter(pk=pk).first()
|
||||
return Response(
|
||||
PageDetailSerializer(page).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
def lock(self, request, slug, project_id, pk):
|
||||
page = Page.objects.filter(
|
||||
pk=page_id, workspace__slug=slug, project_id=project_id
|
||||
pk=pk, workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
|
||||
page.is_locked = True
|
||||
page.save()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def unlock(self, request, slug, project_id, page_id):
|
||||
def unlock(self, request, slug, project_id, pk):
|
||||
page = Page.objects.filter(
|
||||
pk=page_id, workspace__slug=slug, project_id=project_id
|
||||
pk=pk, workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
|
||||
page.is_locked = False
|
||||
|
|
@ -160,13 +193,13 @@ class PageViewSet(BaseViewSet):
|
|||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def list(self, request, slug, project_id):
|
||||
queryset = self.get_queryset().filter(archived_at__isnull=True)
|
||||
queryset = self.get_queryset()
|
||||
pages = PageSerializer(queryset, many=True).data
|
||||
return Response(pages, status=status.HTTP_200_OK)
|
||||
|
||||
def archive(self, request, slug, project_id, page_id):
|
||||
def archive(self, request, slug, project_id, pk):
|
||||
page = Page.objects.get(
|
||||
pk=page_id, workspace__slug=slug, project_id=project_id
|
||||
pk=pk, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
|
||||
# only the owner or admin can archive the page
|
||||
|
|
@ -184,13 +217,16 @@ class PageViewSet(BaseViewSet):
|
|||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
unarchive_archive_page_and_descendants(page_id, datetime.now())
|
||||
unarchive_archive_page_and_descendants(pk, datetime.now())
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
return Response(
|
||||
{"archived_at": str(datetime.now())},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
def unarchive(self, request, slug, project_id, page_id):
|
||||
def unarchive(self, request, slug, project_id, pk):
|
||||
page = Page.objects.get(
|
||||
pk=page_id, workspace__slug=slug, project_id=project_id
|
||||
pk=pk, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
|
||||
# only the owner or admin can un archive the page
|
||||
|
|
@ -213,19 +249,10 @@ class PageViewSet(BaseViewSet):
|
|||
page.parent = None
|
||||
page.save(update_fields=["parent"])
|
||||
|
||||
unarchive_archive_page_and_descendants(page_id, None)
|
||||
unarchive_archive_page_and_descendants(pk, None)
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def archive_list(self, request, slug, project_id):
|
||||
pages = Page.objects.filter(
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
).filter(archived_at__isnull=False)
|
||||
|
||||
pages = PageSerializer(pages, many=True).data
|
||||
return Response(pages, status=status.HTTP_200_OK)
|
||||
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
page = Page.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id
|
||||
|
|
@ -269,29 +296,20 @@ class PageFavoriteViewSet(BaseViewSet):
|
|||
serializer_class = PageFavoriteSerializer
|
||||
model = PageFavorite
|
||||
|
||||
def get_queryset(self):
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(archived_at__isnull=True)
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(user=self.request.user)
|
||||
.select_related("page", "page__owned_by")
|
||||
def create(self, request, slug, project_id, pk):
|
||||
_ = PageFavorite.objects.create(
|
||||
project_id=project_id,
|
||||
page_id=pk,
|
||||
user=request.user,
|
||||
)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def create(self, request, slug, project_id):
|
||||
serializer = PageFavoriteSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(user=request.user, project_id=project_id)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def destroy(self, request, slug, project_id, page_id):
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
page_favorite = PageFavorite.objects.get(
|
||||
project=project_id,
|
||||
user=request.user,
|
||||
workspace__slug=slug,
|
||||
page_id=page_id,
|
||||
page_id=pk,
|
||||
)
|
||||
page_favorite.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from plane.app.permissions import (
|
|||
from plane.db.models import State, Issue
|
||||
from plane.utils.cache import invalidate_cache
|
||||
|
||||
|
||||
class StateViewSet(BaseViewSet):
|
||||
serializer_class = StateSerializer
|
||||
model = State
|
||||
|
|
@ -38,7 +39,9 @@ class StateViewSet(BaseViewSet):
|
|||
.distinct()
|
||||
)
|
||||
|
||||
@invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False)
|
||||
@invalidate_cache(
|
||||
path="workspaces/:slug/states/", url_params=True, user=False
|
||||
)
|
||||
def create(self, request, slug, project_id):
|
||||
serializer = StateSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
|
|
@ -59,7 +62,9 @@ class StateViewSet(BaseViewSet):
|
|||
return Response(state_dict, status=status.HTTP_200_OK)
|
||||
return Response(states, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False)
|
||||
@invalidate_cache(
|
||||
path="workspaces/:slug/states/", url_params=True, user=False
|
||||
)
|
||||
def mark_as_default(self, request, slug, project_id, pk):
|
||||
# Select all the states which are marked as default
|
||||
_ = State.objects.filter(
|
||||
|
|
@ -70,7 +75,9 @@ class StateViewSet(BaseViewSet):
|
|||
).update(default=True)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False)
|
||||
@invalidate_cache(
|
||||
path="workspaces/:slug/states/", url_params=True, user=False
|
||||
)
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
state = State.objects.get(
|
||||
is_triage=False,
|
||||
|
|
|
|||
|
|
@ -326,11 +326,11 @@ class IssueViewFavoriteViewSet(BaseViewSet):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def destroy(self, request, slug, project_id, view_id):
|
||||
view_favourite = IssueViewFavorite.objects.get(
|
||||
view_favorite = IssueViewFavorite.objects.get(
|
||||
project=project_id,
|
||||
user=request.user,
|
||||
workspace__slug=slug,
|
||||
view_id=view_id,
|
||||
)
|
||||
view_favourite.delete()
|
||||
view_favorite.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
|||
76
apiserver/plane/bgtasks/page_transaction_task.py
Normal file
76
apiserver/plane/bgtasks/page_transaction_task.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# Python imports
|
||||
import json
|
||||
|
||||
# Django imports
|
||||
from django.utils import timezone
|
||||
|
||||
# Third-party imports
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Page, PageLog
|
||||
from celery import shared_task
|
||||
|
||||
|
||||
def extract_components(value, tag):
|
||||
try:
|
||||
mentions = []
|
||||
html = value.get("description_html")
|
||||
soup = BeautifulSoup(html, "html.parser")
|
||||
mention_tags = soup.find_all(tag)
|
||||
|
||||
for mention_tag in mention_tags:
|
||||
mention = {
|
||||
"id": mention_tag.get("id"),
|
||||
"entity_identifier": mention_tag.get("entity_identifier"),
|
||||
"entity_name": mention_tag.get("entity_name"),
|
||||
}
|
||||
mentions.append(mention)
|
||||
|
||||
return mentions
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
@shared_task
|
||||
def page_transaction(new_value, old_value, page_id):
|
||||
page = Page.objects.get(pk=page_id)
|
||||
new_page_mention = PageLog.objects.filter(page_id=page_id).exists()
|
||||
|
||||
old_value = json.loads(old_value)
|
||||
|
||||
new_transactions = []
|
||||
deleted_transaction_ids = set()
|
||||
|
||||
# TODO - Add "issue-embed-component", "img", "todo" components
|
||||
components = ["mention-component"]
|
||||
for component in components:
|
||||
old_mentions = extract_components(old_value, component)
|
||||
new_mentions = extract_components(new_value, component)
|
||||
|
||||
new_mentions_ids = {mention["id"] for mention in new_mentions}
|
||||
old_mention_ids = {mention["id"] for mention in old_mentions}
|
||||
deleted_transaction_ids.update(old_mention_ids - new_mentions_ids)
|
||||
|
||||
new_transactions.extend(
|
||||
PageLog(
|
||||
transaction=mention["id"],
|
||||
page_id=page_id,
|
||||
entity_identifier=mention["entity_identifier"],
|
||||
entity_name=mention["entity_name"],
|
||||
workspace_id=page.workspace_id,
|
||||
project_id=page.project_id,
|
||||
created_at=timezone.now(),
|
||||
updated_at=timezone.now(),
|
||||
)
|
||||
for mention in new_mentions
|
||||
if mention["id"] not in old_mention_ids or not new_page_mention
|
||||
)
|
||||
|
||||
# Create new PageLog objects for new transactions
|
||||
PageLog.objects.bulk_create(new_transactions, batch_size=10, ignore_conflicts=True)
|
||||
|
||||
# Delete the removed transactions
|
||||
PageLog.objects.filter(
|
||||
transaction__in=deleted_transaction_ids
|
||||
).delete()
|
||||
20
apiserver/plane/db/migrations/0064_auto_20240409_1134.py
Normal file
20
apiserver/plane/db/migrations/0064_auto_20240409_1134.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 4.2.10 on 2024-04-09 11:34
|
||||
|
||||
from django.db import migrations, models
|
||||
import plane.db.models.page
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0063_state_is_triage_alter_state_group'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="page",
|
||||
name="view_props",
|
||||
field=models.JSONField(
|
||||
default=plane.db.models.page.get_view_props
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -9,6 +9,10 @@ from . import ProjectBaseModel
|
|||
from plane.utils.html_processor import strip_tags
|
||||
|
||||
|
||||
def get_view_props():
|
||||
return {"full_width": False}
|
||||
|
||||
|
||||
class Page(ProjectBaseModel):
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.JSONField(default=dict, blank=True)
|
||||
|
|
@ -35,6 +39,7 @@ class Page(ProjectBaseModel):
|
|||
)
|
||||
archived_at = models.DateField(null=True)
|
||||
is_locked = models.BooleanField(default=False)
|
||||
view_props = models.JSONField(default=get_view_props)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Page"
|
||||
|
|
@ -81,7 +86,7 @@ class PageLog(ProjectBaseModel):
|
|||
ordering = ("-created_at",)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.page.name} {self.type}"
|
||||
return f"{self.page.name} {self.entity_name}"
|
||||
|
||||
|
||||
class PageBlock(ProjectBaseModel):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue