Compare commits

...
Author SHA1 Message Date
sriram veeraghanta 86b6a196ce chore: removed package build for utils and hooks 2025-03-26 18:41:08 +05:30
41447e566a [WEB-3600] fix: private project join issue (#6799)
* fix: private project join issue

* chore: return network value

* fix: refactor

* fix: refactor

* fix: type

* chore: added restricition for private projects

* chore: removed extra validations

* chore: added value to access enum

---------

Co-authored-by: sangeethailango <[email protected]>
Co-authored-by: NarayanBavisetti <[email protected]>
2025-03-25 20:17:16 +05:30
Prateek ShouryaandGitHub cebd0b3599 [RANTS-68] fix: z-index for image picker popover button (change project cover) (#6812) 2025-03-25 20:16:06 +05:30
Prateek ShouryaandGitHub caa522118d [RANTS-29] fix: enter key does not work in the workspace member invite modal (#6816) 2025-03-25 20:15:35 +05:30
Prateek ShouryaandGitHub aea5f39059 [RANTS-31] improvement: optimistic update for home widget reordering (#6817) 2025-03-25 20:15:00 +05:30
sriram veeraghanta f29867968a chore: removed sentry instrumentation dependencies 2025-03-25 15:54:44 +05:30
sriram veeraghanta c91972cc0a chore: removing sentry instrumentation 2025-03-25 15:45:31 +05:30
Anmol Singh BhatiaandGitHub 84c7375d2a [WEB-3601] chore: content updated (#6811) 2025-03-24 19:57:13 +05:30
Anmol Singh BhatiaandGitHub 5cb37a0b9c [WEB-3560] fix: table layout issue block and code refactor (#6805) 2025-03-24 19:06:36 +05:30
Anmol Singh BhatiaandGitHub c347dd7dcd [WEB-3614] chore: list layout display filters (#6801) 2025-03-24 19:05:53 +05:30
sriram veeraghanta 0ec206b75d fix: transpile packages update on space and admin apps 2025-03-24 18:55:59 +05:30
Vamsi KrishnaandGitHub e8718a84fe chore: issue detail refactor (#6803) 2025-03-24 18:33:22 +05:30
Anmol Singh BhatiaandGitHub 983e0fa081 [WEB-3438] fix: transfer completed cycle issue modal (#6802) 2025-03-24 18:30:31 +05:30
Aaryan KhandelwalandGitHub ef108839c4 [RANTS-57] chore: replace target date with due date in work item filters dropdown (#6806) 2025-03-24 18:24:10 +05:30
Bavisetti NarayanandGitHub fe04e5a292 [WEB-3658] fix: remove cycles and modules when issues are bulk deleted (#6807) 2025-03-24 18:23:09 +05:30
Aaryan KhandelwalandGitHub 50e0cb7ffd [RANTS-75] chore: update profile sidebar icons and copy for consistency (#6808) 2025-03-24 18:21:12 +05:30
Akshita GoyalandGitHub d37d210921 [WEB-3677] fix: settings dynamic pages permissions (#6804)
* fix: settings dynamic pages permissions

* fix: refactor
2025-03-24 18:15:43 +05:30
Anmol Singh BhatiaandGitHub ab3eadf767 [WEB-3614] fix: cmd-k item focus state (#6800) 2025-03-24 18:13:49 +05:30
sriram veeraghanta dbdf2f001a fix: transpile packages for web application 2025-03-24 13:47:00 +05:30
Prateek ShouryaandGitHub 0d069bf46e [RANTS-65] fix: undefined work item sequence in bulk delete work item modal (#6797) 2025-03-24 13:41:02 +05:30
Prateek ShouryaandGitHub 962923ff4f fix: admin build (#6798) 2025-03-24 13:40:07 +05:30
Samuel TorresandGitHub f720a9afb2 feat: validate github organization during OAuth login (#6700)
* feat: add GITHUB_ORGANIZATION_ID support for GitHub OAuth integration

* fix: remove debug print statements from InstanceConfigurationEndpoint
2025-03-24 12:55:20 +05:30
4032aa62c5 [WEB-3551] fix: role improvements (#6763)
* Return Cycle start and end dates in project's timezone

* fix: role improvements

* chore: role updates

* chore: update role endpoint to update workspace admin permissions

* fix: conditions

* chore: update member role for workspace members

* chore: update workspace permission role

* fix: currentAdmin permissions

---------

Co-authored-by: Dheeraj Kumar Ketireddy <[email protected]>
Co-authored-by: pablohashescobar <[email protected]>
2025-03-24 12:52:57 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
cbe248591e chore(deps): bump next in the npm_and_yarn group across 1 directory (#6796)
Bumps the npm_and_yarn group with 1 update in the / directory: [next](https://github.com/vercel/next.js).


Updates `next` from 14.2.24 to 14.2.25
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.2.24...v14.2.25)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 12:52:16 +05:30
Dheeraj Kumar KetireddyandGitHub 75a9b71edb [WEB-3513] fix: return cycle start and end dates in project's timezone 2025-03-24 12:51:44 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ef42ce04a4 chore(deps): bump gunicorn (#6793)
Bumps the pip group with 1 update in the /apiserver/requirements directory: [gunicorn](https://github.com/benoitc/gunicorn).


Updates `gunicorn` from 22.0.0 to 23.0.0
- [Release notes](https://github.com/benoitc/gunicorn/releases)
- [Commits](https://github.com/benoitc/gunicorn/compare/22.0.0...23.0.0)

---
updated-dependencies:
- dependency-name: gunicorn
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 12:48:05 +05:30
6bafdb6dd8 [PE-298] Fix: Copy markdown to clipboard (#6675)
* fix: markdown for mentions fixed

* fix: copying text in mentions

* fix: refactored the component to use the same function

* chore: renamed funcion name

* add the new copy extension

* init working fix

* remove useless code

* improve readibility

* update node import

* better smaller logic

* remove log

* add open close end handler

* update readabliity

* handle tables

* handle triple click in cell

* triple tap select current line

* handle block and list

* lists fixed

* handle all possible cases of copy in table

* update the min elements

* handle multi types in table

* handle table seletion cases

* handle whole table handler

* feat: all case converd

* update markdown handling code

* update return statement

* handle using group block

* handle param

* handle multple cell in table

* handle using recursion

* add types

* fix code rabbit  suggestions

* fix root node bug

* update recursion with loop

* update transform copied to false

* refactor clipboard extension: remove options and integrate MarkdownClipboard into core extensions

* fix: header and code handler

* fix: store hooks fixed

* fix: mention id

---------

Co-authored-by: Palanikannan M <[email protected]>
2025-03-24 12:32:11 +05:30
sriram veeraghanta 72307ec100 chore: update package version 2025-03-21 17:00:14 +05:30
sriram veeraghanta 2eb1d03c20 fix: transpile and optimize package imports 2025-03-21 01:51:50 +05:30
112 changed files with 746 additions and 1447 deletions
-3
View File
@@ -43,9 +43,6 @@ NGINX_PORT=80
# Debug value for api server use it as 0 for production use
DEBUG=0
CORS_ALLOWED_ORIGINS="http://localhost"
# Error logs
SENTRY_DSN=""
SENTRY_ENVIRONMENT="development"
# Database Settings
POSTGRES_USER="plane"
POSTGRES_PASSWORD="plane"
+15
View File
@@ -43,6 +43,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
defaultValues: {
GITHUB_CLIENT_ID: config["GITHUB_CLIENT_ID"],
GITHUB_CLIENT_SECRET: config["GITHUB_CLIENT_SECRET"],
GITHUB_ORGANIZATION_ID: config["GITHUB_ORGANIZATION_ID"],
},
});
@@ -93,6 +94,19 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
error: Boolean(errors.GITHUB_CLIENT_SECRET),
required: true,
},
{
key: "GITHUB_ORGANIZATION_ID",
type: "text",
label: "Organization ID",
description: (
<>
The organization github ID.
</>
),
placeholder: "123456789",
error: Boolean(errors.GITHUB_ORGANIZATION_ID),
required: false,
},
];
const GITHUB_SERVICE_FIELD: TCopyField[] = [
@@ -150,6 +164,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
reset({
GITHUB_CLIENT_ID: response.find((item) => item.key === "GITHUB_CLIENT_ID")?.value,
GITHUB_CLIENT_SECRET: response.find((item) => item.key === "GITHUB_CLIENT_SECRET")?.value,
GITHUB_ORGANIZATION_ID: response.find((item) => item.key === "GITHUB_ORGANIZATION_ID")?.value,
});
})
.catch((err) => console.error(err));
+13
View File
@@ -9,6 +9,19 @@ const nextConfig = {
unoptimized: true,
},
basePath: process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "",
transpilePackages: [
"@plane/constants",
"@plane/editor",
"@plane/hooks",
"@plane/i18n",
"@plane/logger",
"@plane/propel",
"@plane/services",
"@plane/shared-state",
"@plane/types",
"@plane/ui",
"@plane/utils",
],
};
module.exports = nextConfig;
+2 -3
View File
@@ -1,7 +1,7 @@
{
"name": "admin",
"description": "Admin UI for Plane",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"private": true,
"scripts": {
@@ -21,7 +21,6 @@
"@plane/ui": "*",
"@plane/utils": "*",
"@plane/services": "*",
"@sentry/nextjs": "^8.54.0",
"@tailwindcss/typography": "^0.5.9",
"@types/lodash": "^4.17.0",
"autoprefixer": "10.4.14",
@@ -30,7 +29,7 @@
"lucide-react": "^0.469.0",
"mobx": "^6.12.0",
"mobx-react": "^9.1.1",
"next": "^14.2.20",
"next": "^14.2.25",
"next-themes": "^0.2.1",
"postcss": "^8.4.38",
"react": "^18.3.1",
-3
View File
@@ -145,11 +145,8 @@ RUN chmod +x /app/pg-setup.sh
# APPLICATION ENVIRONMENT SETTINGS
# *****************************************************************************
ENV APP_DOMAIN=localhost
ENV WEB_URL=http://${APP_DOMAIN}
ENV DEBUG=0
ENV SENTRY_DSN=
ENV SENTRY_ENVIRONMENT=production
ENV CORS_ALLOWED_ORIGINS=http://${APP_DOMAIN},https://${APP_DOMAIN}
# Secret Key
ENV SECRET_KEY=60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5
-4
View File
@@ -3,10 +3,6 @@
DEBUG=0
CORS_ALLOWED_ORIGINS="http://localhost"
# Error logs
SENTRY_DSN=""
SENTRY_ENVIRONMENT="development"
# Database Settings
POSTGRES_USER="plane"
POSTGRES_PASSWORD="plane"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "plane-api",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"private": true,
"description": "API server powering Plane's backend"
+9
View File
@@ -1,4 +1,5 @@
# Third party imports
import pytz
from rest_framework import serializers
# Module imports
@@ -18,6 +19,14 @@ class CycleSerializer(BaseSerializer):
completed_estimates = serializers.FloatField(read_only=True)
started_estimates = serializers.FloatField(read_only=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
project = self.context.get("project")
if project and project.timezone:
project_timezone = pytz.timezone(project.timezone)
self.fields["start_date"].timezone = project_timezone
self.fields["end_date"].timezone = project_timezone
def validate(self, data):
if (
data.get("start_date", None) is not None
+15 -7
View File
@@ -137,10 +137,12 @@ class CycleAPIEndpoint(BaseAPIView):
)
def get(self, request, slug, project_id, pk=None):
project = Project.objects.get(workspace__slug=slug, pk=project_id)
if pk:
queryset = self.get_queryset().filter(archived_at__isnull=True).get(pk=pk)
data = CycleSerializer(
queryset, fields=self.fields, expand=self.expand
queryset, fields=self.fields,
expand=self.expand, context={"project": project}
).data
return Response(data, status=status.HTTP_200_OK)
queryset = self.get_queryset().filter(archived_at__isnull=True)
@@ -152,7 +154,8 @@ class CycleAPIEndpoint(BaseAPIView):
start_date__lte=timezone.now(), end_date__gte=timezone.now()
)
data = CycleSerializer(
queryset, many=True, fields=self.fields, expand=self.expand
queryset, many=True, fields=self.fields,
expand=self.expand, context={"project": project}
).data
return Response(data, status=status.HTTP_200_OK)
@@ -163,7 +166,8 @@ class CycleAPIEndpoint(BaseAPIView):
request=request,
queryset=(queryset),
on_results=lambda cycles: CycleSerializer(
cycles, many=True, fields=self.fields, expand=self.expand
cycles, many=True, fields=self.fields,
expand=self.expand, context={"project": project}
).data,
)
@@ -174,7 +178,8 @@ class CycleAPIEndpoint(BaseAPIView):
request=request,
queryset=(queryset),
on_results=lambda cycles: CycleSerializer(
cycles, many=True, fields=self.fields, expand=self.expand
cycles, many=True, fields=self.fields,
expand=self.expand, context={"project": project}
).data,
)
@@ -185,7 +190,8 @@ class CycleAPIEndpoint(BaseAPIView):
request=request,
queryset=(queryset),
on_results=lambda cycles: CycleSerializer(
cycles, many=True, fields=self.fields, expand=self.expand
cycles, many=True, fields=self.fields,
expand=self.expand, context={"project": project}
).data,
)
@@ -198,14 +204,16 @@ class CycleAPIEndpoint(BaseAPIView):
request=request,
queryset=(queryset),
on_results=lambda cycles: CycleSerializer(
cycles, many=True, fields=self.fields, expand=self.expand
cycles, many=True, fields=self.fields,
expand=self.expand, context={"project": project}
).data,
)
return self.paginate(
request=request,
queryset=(queryset),
on_results=lambda cycles: CycleSerializer(
cycles, many=True, fields=self.fields, expand=self.expand
cycles, many=True, fields=self.fields,
expand=self.expand, context={"project": project}
).data,
)
+15 -6
View File
@@ -268,7 +268,7 @@ class CycleViewSet(BaseViewSet):
)
datetime_fields = ["start_date", "end_date"]
data = user_timezone_converter(
data, datetime_fields, request.user.user_timezone
data, datetime_fields, project_timezone
)
return Response(data, status=status.HTTP_200_OK)
@@ -318,9 +318,13 @@ class CycleViewSet(BaseViewSet):
.first()
)
# Fetch the project timezone
project = Project.objects.get(id=self.kwargs.get("project_id"))
project_timezone = project.timezone
datetime_fields = ["start_date", "end_date"]
cycle = user_timezone_converter(
cycle, datetime_fields, request.user.user_timezone
cycle, datetime_fields, project_timezone
)
# Send the model activity
@@ -407,9 +411,13 @@ class CycleViewSet(BaseViewSet):
"created_by",
).first()
# Fetch the project timezone
project = Project.objects.get(id=self.kwargs.get("project_id"))
project_timezone = project.timezone
datetime_fields = ["start_date", "end_date"]
cycle = user_timezone_converter(
cycle, datetime_fields, request.user.user_timezone
cycle, datetime_fields, project_timezone
)
# Send the model activity
@@ -480,10 +488,11 @@ class CycleViewSet(BaseViewSet):
)
queryset = queryset.first()
# Fetch the project timezone
project = Project.objects.get(id=self.kwargs.get("project_id"))
project_timezone = project.timezone
datetime_fields = ["start_date", "end_date"]
data = user_timezone_converter(
data, datetime_fields, request.user.user_timezone
)
data = user_timezone_converter(data, datetime_fields, project_timezone)
recent_visited_task.delay(
slug=slug,
+8
View File
@@ -45,6 +45,7 @@ from plane.db.models import (
ProjectMember,
CycleIssue,
UserRecentVisit,
ModuleIssue,
)
from plane.utils.grouper import (
issue_group_values,
@@ -738,6 +739,13 @@ class BulkDeleteIssuesEndpoint(BaseAPIView):
total_issues = len(issues)
# First, delete all related cycle issues
CycleIssue.objects.filter(issue_id__in=issue_ids).delete()
# Then, delete all related module issues
ModuleIssue.objects.filter(issue_id__in=issue_ids).delete()
# Finally, delete the issues themselves
issues.delete()
return Response(
@@ -179,6 +179,7 @@ class ProjectViewSet(BaseViewSet):
"inbox_view",
"guest_view_all_features",
"project_lead",
"network",
"created_at",
"updated_at",
"created_by",
+17 -7
View File
@@ -16,17 +16,17 @@ from rest_framework.permissions import AllowAny
# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.app.serializers import ProjectMemberInviteSerializer
from plane.app.permissions import allow_permission, ROLE
from plane.db.models import (
ProjectMember,
Workspace,
ProjectMemberInvite,
User,
WorkspaceMember,
Project,
IssueUserProperty,
)
from plane.db.models.project import ProjectNetwork
class ProjectInvitationsViewset(BaseViewSet):
@@ -128,6 +128,7 @@ class UserProjectInvitationsViewset(BaseViewSet):
.select_related("workspace", "workspace__owner", "project")
)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE")
def create(self, request, slug):
project_ids = request.data.get("project_ids", [])
@@ -136,11 +137,20 @@ class UserProjectInvitationsViewset(BaseViewSet):
member=request.user, workspace__slug=slug, is_active=True
)
if workspace_member.role not in [ROLE.ADMIN.value, ROLE.MEMBER.value]:
return Response(
{"error": "You do not have permission to join the project"},
status=status.HTTP_403_FORBIDDEN,
)
# Get all the projects
projects = Project.objects.filter(
id__in=project_ids, workspace__slug=slug
).only("id", "network")
# Check if user has permission to join each project
for project in projects:
if (
project.network == ProjectNetwork.SECRET.value
and workspace_member.role != ROLE.ADMIN.value
):
return Response(
{"error": "Only workspace admins can join private project"},
status=status.HTTP_403_FORBIDDEN,
)
workspace_role = workspace_member.role
workspace = workspace_member.workspace
+12 -18
View File
@@ -10,11 +10,7 @@ from plane.app.serializers import (
ProjectMemberRoleSerializer,
)
from plane.app.permissions import (
ProjectMemberPermission,
ProjectLitePermission,
WorkspaceUserPermission,
)
from plane.app.permissions import WorkspaceUserPermission
from plane.db.models import Project, ProjectMember, IssueUserProperty, WorkspaceMember
from plane.bgtasks.project_add_user_email_task import project_add_user_email
@@ -26,14 +22,6 @@ class ProjectMemberViewSet(BaseViewSet):
serializer_class = ProjectMemberAdminSerializer
model = ProjectMember
def get_permissions(self):
if self.action == "leave":
self.permission_classes = [ProjectLitePermission]
else:
self.permission_classes = [ProjectMemberPermission]
return super(ProjectMemberViewSet, self).get_permissions()
search_fields = ["member__display_name", "member__first_name"]
def get_queryset(self):
@@ -187,12 +175,20 @@ class ProjectMemberViewSet(BaseViewSet):
)
return Response(serializer.data, status=status.HTTP_200_OK)
@allow_permission([ROLE.ADMIN])
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def partial_update(self, request, slug, project_id, pk):
project_member = ProjectMember.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id, is_active=True
)
if request.user.id == project_member.member_id:
# Fetch the workspace role of the project member
workspace_role = WorkspaceMember.objects.get(
workspace__slug=slug, member=project_member.member, is_active=True
).role
is_workspace_admin = workspace_role == ROLE.ADMIN.value
# Check if the user is not editing their own role if they are not an admin
if request.user.id == project_member.member_id and not is_workspace_admin:
return Response(
{"error": "You cannot update your own role"},
status=status.HTTP_400_BAD_REQUEST,
@@ -205,9 +201,6 @@ class ProjectMemberViewSet(BaseViewSet):
is_active=True,
)
workspace_role = WorkspaceMember.objects.get(
workspace__slug=slug, member=project_member.member, is_active=True
).role
if workspace_role in [5] and int(
request.data.get("role", project_member.role)
) in [15, 20]:
@@ -222,6 +215,7 @@ class ProjectMemberViewSet(BaseViewSet):
"role" in request.data
and int(request.data.get("role", project_member.role))
> requested_project_member.role
and not is_workspace_admin
):
return Response(
{"error": "You cannot update a role that is higher than your own role"},
@@ -68,10 +68,11 @@ class WorkSpaceMemberViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST,
)
if workspace_member.role > int(request.data.get("role")):
_ = ProjectMember.objects.filter(
# If a user is moved to a guest role he can't have any other role in projects
if "role" in request.data and int(request.data.get("role")) == 5:
ProjectMember.objects.filter(
workspace__slug=slug, member_id=workspace_member.member_id
).update(role=int(request.data.get("role")))
).update(role=5)
serializer = WorkSpaceMemberSerializer(
workspace_member, data=request.data, partial=True
@@ -36,10 +36,12 @@ AUTHENTICATION_ERROR_CODES = {
"OAUTH_NOT_CONFIGURED": 5104,
"GOOGLE_NOT_CONFIGURED": 5105,
"GITHUB_NOT_CONFIGURED": 5110,
"GITHUB_USER_NOT_IN_ORG": 5122,
"GITLAB_NOT_CONFIGURED": 5111,
"GOOGLE_OAUTH_PROVIDER_ERROR": 5115,
"GITHUB_OAUTH_PROVIDER_ERROR": 5120,
"GITLAB_OAUTH_PROVIDER_ERROR": 5121,
# Reset Password
"INVALID_PASSWORD_TOKEN": 5125,
"EXPIRED_PASSWORD_TOKEN": 5130,
@@ -18,11 +18,16 @@ from plane.authentication.adapter.error import (
class GitHubOAuthProvider(OauthAdapter):
token_url = "https://github.com/login/oauth/access_token"
userinfo_url = "https://api.github.com/user"
org_membership_url = f"https://api.github.com/orgs"
provider = "github"
scope = "read:user user:email"
organization_scope = "read:org"
def __init__(self, request, code=None, state=None, callback=None):
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET = get_configuration_value(
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GITHUB_ORGANIZATION_ID = get_configuration_value(
[
{
"key": "GITHUB_CLIENT_ID",
@@ -32,6 +37,10 @@ class GitHubOAuthProvider(OauthAdapter):
"key": "GITHUB_CLIENT_SECRET",
"default": os.environ.get("GITHUB_CLIENT_SECRET"),
},
{
"key": "GITHUB_ORGANIZATION_ID",
"default": os.environ.get("GITHUB_ORGANIZATION_ID"),
},
]
)
@@ -43,6 +52,10 @@ class GitHubOAuthProvider(OauthAdapter):
client_id = GITHUB_CLIENT_ID
client_secret = GITHUB_CLIENT_SECRET
self.organization_id = GITHUB_ORGANIZATION_ID
if self.organization_id:
self.scope += f" {self.organization_scope}"
redirect_uri = f"""{"https" if request.is_secure() else "http"}://{request.get_host()}/auth/github/callback/"""
url_params = {
@@ -113,12 +126,26 @@ class GitHubOAuthProvider(OauthAdapter):
error_message="GITHUB_OAUTH_PROVIDER_ERROR",
)
def is_user_in_organization(self, github_username):
headers = {"Authorization": f"Bearer {self.token_data.get('access_token')}"}
response = requests.get(f"{self.org_membership_url}/{self.organization_id}/memberships/{github_username}", headers=headers)
return response.status_code == 200 # 200 means the user is a member
def set_user_data(self):
user_info_response = self.get_user_response()
headers = {
"Authorization": f"Bearer {self.token_data.get('access_token')}",
"Accept": "application/json",
}
if self.organization_id:
if not self.is_user_in_organization(user_info_response.get("login")):
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["GITHUB_USER_NOT_IN_ORG"],
error_message="GITHUB_USER_NOT_IN_ORG",
)
email = self.__get_email(headers=headers)
super().set_user_data(
{
@@ -3,7 +3,6 @@
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from sentry_sdk import capture_exception
import uuid
@@ -29,7 +28,6 @@ def create_issue_relation(apps, schema_editor):
)
except Exception as e:
print(e)
capture_exception(e)
def update_issue_priority_choice(apps, schema_editor):
+10
View File
@@ -1,6 +1,7 @@
# Python imports
import pytz
from uuid import uuid4
from enum import Enum
# Django imports
from django.conf import settings
@@ -17,6 +18,15 @@ from .base import BaseModel
ROLE_CHOICES = ((20, "Admin"), (15, "Member"), (5, "Guest"))
class ProjectNetwork(Enum):
SECRET = 0
PUBLIC = 2
@classmethod
def choices(cls):
return [(0, "Secret"), (2, "Public")]
def get_default_props():
return {
"filters": {
@@ -71,6 +71,12 @@ class Command(BaseCommand):
"category": "GITHUB",
"is_encrypted": True,
},
{
"key": "GITHUB_ORGANIZATION_ID",
"value": os.environ.get("GITHUB_ORGANIZATION_ID"),
"category": "GITHUB",
"is_encrypted": False,
},
{
"key": "GITLAB_HOST",
"value": os.environ.get("GITLAB_HOST"),
-23
View File
@@ -7,13 +7,9 @@ from urllib.parse import urlparse
# Third party imports
import dj_database_url
import sentry_sdk
# Django imports
from django.core.management.utils import get_random_secret_key
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration
from corsheaders.defaults import default_headers
@@ -267,25 +263,6 @@ CELERY_IMPORTS = (
"plane.bgtasks.issue_description_version_sync",
)
# Sentry Settings
# Enable Sentry Settings
if bool(os.environ.get("SENTRY_DSN", False)) and os.environ.get(
"SENTRY_DSN"
).startswith("https://"):
sentry_sdk.init(
dsn=os.environ.get("SENTRY_DSN", ""),
integrations=[
DjangoIntegration(),
RedisIntegration(),
CeleryIntegration(monitor_beat_tasks=True),
],
traces_sample_rate=1,
send_default_pii=True,
environment=os.environ.get("SENTRY_ENVIRONMENT", "development"),
profiles_sample_rate=float(os.environ.get("SENTRY_PROFILE_SAMPLE_RATE", 0)),
)
FILE_SIZE_LIMIT = int(os.environ.get("FILE_SIZE_LIMIT", 5242880))
# Unsplash Access key
@@ -5,9 +5,6 @@ import traceback
# Django imports
from django.conf import settings
# Third party imports
from sentry_sdk import capture_exception
def log_exception(e):
# Log the error
@@ -18,6 +15,4 @@ def log_exception(e):
# Print the traceback if in debug mode
print(traceback.format_exc())
# Capture in sentry if configured
capture_exception(e)
return
-2
View File
@@ -26,8 +26,6 @@ faker==25.0.0
django-filter==24.2
# json model
jsonmodels==2.7.0
# sentry
sentry-sdk==2.8.0
# storage
django-storages==1.14.2
# user management
+1 -1
View File
@@ -1,3 +1,3 @@
-r base.txt
# server
gunicorn==22.0.0
gunicorn==23.0.0
+3 -15
View File
@@ -6,16 +6,8 @@
"website": "https://plane.so/",
"success_url": "/",
"stack": "heroku-22",
"keywords": [
"plane",
"project management",
"django",
"next"
],
"addons": [
"heroku-postgresql:mini",
"heroku-redis:mini"
],
"keywords": ["plane", "project management", "django", "next"],
"addons": ["heroku-postgresql:mini", "heroku-redis:mini"],
"buildpacks": [
{
"url": "https://github.com/heroku/heroku-buildpack-python.git"
@@ -61,10 +53,6 @@
"description": "AWS Bucket Name to use for S3",
"value": ""
},
"SENTRY_DSN": {
"description": "",
"value": ""
},
"WEB_URL": {
"description": "Web URL for Plane this will be used for redirections in the emails",
"value": ""
@@ -82,4 +70,4 @@
"value": ""
}
}
}
}
-2
View File
@@ -42,8 +42,6 @@ x-live-env: &live-env
x-app-env: &app-env
WEB_URL: ${WEB_URL:-http://localhost}
DEBUG: ${DEBUG:-0}
SENTRY_DSN: ${SENTRY_DSN}
SENTRY_ENVIRONMENT: ${SENTRY_ENVIRONMENT:-production}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS}
GUNICORN_WORKERS: 1
USE_MINIO: ${USE_MINIO:-1}
-2
View File
@@ -12,8 +12,6 @@ LIVE_REPLICAS=1
NGINX_PORT=80
WEB_URL=http://${APP_DOMAIN}
DEBUG=0
SENTRY_DSN=
SENTRY_ENVIRONMENT=production
CORS_ALLOWED_ORIGINS=http://${APP_DOMAIN}
API_BASE_URL=http://api:8000
+1 -3
View File
@@ -1,6 +1,6 @@
{
"name": "live",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"description": "A realtime collaborative server powers Plane's rich text editor",
"main": "./src/server.ts",
@@ -23,8 +23,6 @@
"@plane/constants": "*",
"@plane/editor": "*",
"@plane/types": "*",
"@sentry/node": "^9.0.1",
"@sentry/profiling-node": "^8.28.0",
"@tiptap/core": "2.10.4",
"@tiptap/html": "2.11.0",
"axios": "^1.8.3",
-19
View File
@@ -1,19 +0,0 @@
import * as Sentry from "@sentry/node";
import { nodeProfilingIntegration } from "@sentry/profiling-node";
// Ensure to call this before importing any other modules!
Sentry.init({
dsn: process.env.LIVE_SENTRY_DSN,
environment: process.env.LIVE_SENTRY_ENVIRONMENT || "development",
integrations: [
// Add our Profiling integration
nodeProfilingIntegration(),
],
// Add Tracing by setting tracesSampleRate
// We recommend adjusting this value in production
tracesSampleRate: Number(process.env.LIVE_SENTRY_TRACES_SAMPLE_RATE) || 0.5,
// Set sampling rate for profiling
// This is relative to tracesSampleRate
profilesSampleRate: 1.0,
});
+1 -6
View File
@@ -1,11 +1,8 @@
import * as Sentry from "@sentry/node";
import compression from "compression";
import cors from "cors";
import expressWs from "express-ws";
import express from "express";
import helmet from "helmet";
// config
import "@/core/config/sentry-config.js";
// hocuspocus server
import { getHocusPocusServer } from "@/core/hocuspocus-server.js";
// helpers
@@ -15,7 +12,7 @@ import { errorHandler } from "@/core/helpers/error-handler.js";
// types
import { TConvertDocumentRequestBody } from "@/core/types/common.js";
const app = express();
const app: any = express();
expressWs(app);
app.set("port", process.env.PORT || 3000);
@@ -92,8 +89,6 @@ app.use((_req, res) => {
res.status(404).send("Not Found");
});
Sentry.setupExpressErrorHandler(app);
app.use(errorHandler);
const liveServer = app.listen(app.get("port"), () => {
-3
View File
@@ -16,9 +16,6 @@
"skipLibCheck": true,
"sourceMap": true,
"inlineSources": true,
// Set `sourceRoot` to "/" to strip the build path prefix
// from generated source code references.
// This improves issue grouping in Sentry.
"sourceRoot": "/"
},
"include": ["src/**/*.ts", "tsup.config.ts"],
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "plane",
"description": "Open-source project management that unlocks customer value",
"repository": "https://github.com/makeplane/plane.git",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"private": true,
"workspaces": [
+3
View File
@@ -0,0 +1,3 @@
build/*
dist/*
out/*
+6
View File
@@ -0,0 +1,6 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
root: true,
extends: ["@plane/eslint-config/library.js"],
parser: "@typescript-eslint/parser",
};
+6 -2
View File
@@ -1,7 +1,11 @@
{
"name": "@plane/constants",
"version": "0.25.2",
"version": "0.25.3",
"private": true,
"main": "./src/index.ts",
"license": "AGPL-3.0"
"license": "AGPL-3.0",
"scripts": {
"lint": "eslint src --ext .ts,.tsx",
"lint:errors": "eslint src --ext .ts,.tsx --quiet"
}
}
+1
View File
@@ -339,6 +339,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_PAGE: TIssueFiltersToDisplayByPageType = {
"-updated_at",
"start_date",
"-priority",
"target_date",
],
type: [null, "active", "backlog"],
},
+6 -2
View File
@@ -83,14 +83,14 @@ export const WORKSPACE_SETTINGS = {
key: "general",
i18n_label: "workspace_settings.settings.general.title",
href: `/settings`,
access: [EUserWorkspaceRoles.ADMIN],
access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/`,
},
members: {
key: "members",
i18n_label: "workspace_settings.settings.members.title",
href: `/settings/members`,
access: [EUserWorkspaceRoles.ADMIN],
access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`,
},
"billing-and-plans": {
@@ -123,6 +123,10 @@ export const WORKSPACE_SETTINGS = {
},
};
export const WORKSPACE_SETTINGS_ACCESS = Object.fromEntries(
Object.entries(WORKSPACE_SETTINGS).map(([_, { href, access }]) => [href, access])
);
export const WORKSPACE_SETTINGS_LINKS: {
key: string;
i18n_label: string;
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "@plane/typescript-config/react-library.json",
"compilerOptions": {
"jsx": "react",
"lib": ["esnext", "dom"]
},
"include": ["./src"],
"exclude": ["dist", "build", "node_modules"]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/editor",
"version": "0.25.2",
"version": "0.25.3",
"description": "Core Editor that powers Plane",
"license": "AGPL-3.0",
"private": true,
@@ -0,0 +1,89 @@
import { Extension } from "@tiptap/core";
import { Fragment, Node } from "@tiptap/pm/model";
import { Plugin, PluginKey } from "@tiptap/pm/state";
export const MarkdownClipboard = Extension.create({
name: "markdownClipboard",
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey("markdownClipboard"),
props: {
clipboardTextSerializer: (slice) => {
const markdownSerializer = this.editor.storage.markdown.serializer;
const isTableRow = slice.content.firstChild?.type?.name === "tableRow";
const nodeSelect = slice.openStart === 0 && slice.openEnd === 0;
if (nodeSelect) {
return markdownSerializer.serialize(slice.content);
}
const processTableContent = (tableNode: Node | Fragment) => {
let result = "";
tableNode.content?.forEach?.((tableRowNode: Node | Fragment) => {
tableRowNode.content?.forEach?.((cell: Node) => {
const cellContent = cell.content ? markdownSerializer.serialize(cell.content) : "";
result += cellContent + "\n";
});
});
return result;
};
if (isTableRow) {
const rowsCount = slice.content?.childCount || 0;
const cellsCount = slice.content?.firstChild?.content?.childCount || 0;
if (rowsCount === 1 || cellsCount === 1) {
return processTableContent(slice.content);
} else {
return markdownSerializer.serialize(slice.content);
}
}
const traverseToParentOfLeaf = (
node: Node | null,
parent: Fragment | Node,
depth: number
): Node | Fragment => {
let currentNode = node;
let currentParent = parent;
let currentDepth = depth;
while (currentNode && currentDepth > 1 && currentNode.content?.firstChild) {
if (currentNode.content?.childCount > 1) {
if (currentNode.content.firstChild?.type?.name === "listItem") {
return currentParent;
} else {
return currentNode.content;
}
}
currentParent = currentNode;
currentNode = currentNode.content?.firstChild || null;
currentDepth--;
}
return currentParent;
};
if (slice.content.childCount > 1) {
return markdownSerializer.serialize(slice.content);
} else {
const targetNode = traverseToParentOfLeaf(slice.content.firstChild, slice.content, slice.openStart);
let currentNode = targetNode;
while (currentNode && currentNode.content && currentNode.childCount === 1 && currentNode.firstChild) {
currentNode = currentNode.firstChild;
}
if (currentNode instanceof Node && currentNode.isText) {
return currentNode.text;
}
return markdownSerializer.serialize(targetNode);
}
},
},
}),
];
},
});
@@ -29,6 +29,7 @@ import {
TableCell,
TableHeader,
TableRow,
MarkdownClipboard,
} from "@/extensions";
// helpers
import { isValidHttpUrl } from "@/helpers/common";
@@ -130,10 +131,11 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
CustomCodeInlineExtension,
Markdown.configure({
html: true,
transformCopiedText: true,
transformCopiedText: false,
transformPastedText: true,
breaks: true,
}),
MarkdownClipboard,
Table,
TableHeader,
TableCell,
@@ -23,3 +23,4 @@ export * from "./quote";
export * from "./read-only-extensions";
export * from "./side-menu";
export * from "./text-align";
export * from "./clipboard";
@@ -1,12 +1,15 @@
import { mergeAttributes } from "@tiptap/core";
import Mention, { MentionOptions } from "@tiptap/extension-mention";
import { MarkdownSerializerState } from "@tiptap/pm/markdown";
import { Node as NodeType } from "@tiptap/pm/model";
// types
import { TMentionHandler } from "@/types";
// local types
import { EMentionComponentAttributeNames } from "./types";
import { EMentionComponentAttributeNames, TMentionComponentAttributes } from "./types";
export type TMentionExtensionOptions = MentionOptions & {
renderComponent: TMentionHandler["renderComponent"];
getMentionedEntityDetails: TMentionHandler["getMentionedEntityDetails"];
};
export const CustomMentionExtensionConfig = Mention.extend<TMentionExtensionOptions>({
@@ -40,9 +43,26 @@ export const CustomMentionExtensionConfig = Mention.extend<TMentionExtensionOpti
class: "mention",
},
addStorage(this) {
renderText({ node }) {
return getMentionDisplayText(this.options, node);
},
addStorage() {
const options = this.options;
return {
mentionsOpen: false,
markdown: {
serialize(state: MarkdownSerializerState, node: NodeType) {
state.write(getMentionDisplayText(options, node));
},
},
};
},
});
function getMentionDisplayText(options: TMentionExtensionOptions, node: NodeType): string {
const attrs = node.attrs as TMentionComponentAttributes;
const mentionEntityId = attrs[EMentionComponentAttributeNames.ENTITY_IDENTIFIER];
const mentionEntityDetails = options.getMentionedEntityDetails?.(mentionEntityId ?? "");
return `@${mentionEntityDetails?.display_name ?? attrs[EMentionComponentAttributeNames.ID] ?? mentionEntityId}`;
}
@@ -9,12 +9,13 @@ import { MentionNodeView } from "./mention-node-view";
import { renderMentionsDropdown } from "./utils";
export const CustomMentionExtension = (props: TMentionHandler) => {
const { searchCallback, renderComponent } = props;
const { searchCallback, renderComponent, getMentionedEntityDetails } = props;
return CustomMentionExtensionConfig.extend({
addOptions(this) {
return {
...this.parent?.(),
renderComponent,
getMentionedEntityDetails,
};
},
@@ -24,6 +24,7 @@ import {
CustomTextAlignExtension,
CustomCalloutReadOnlyExtension,
CustomColorExtension,
MarkdownClipboard,
} from "@/extensions";
// helpers
import { isValidHttpUrl } from "@/helpers/common";
@@ -114,8 +115,9 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => {
CustomCodeInlineExtension,
Markdown.configure({
html: true,
transformCopiedText: true,
transformCopiedText: false,
}),
MarkdownClipboard,
Table,
TableHeader,
TableCell,
@@ -16,6 +16,22 @@ export function tableControls() {
},
},
props: {
handleTripleClickOn(view, pos, node, nodePos, event, direct) {
if (node.type.name === 'tableCell') {
event.preventDefault();
const $pos = view.state.doc.resolve(pos);
const line = $pos.parent;
const linePos = $pos.start();
const start = linePos;
const end = linePos + line.nodeSize - 1;
const tr = view.state.tr.setSelection(
TextSelection.create(view.state.doc, start, end)
);
view.dispatch(tr);
return true;
}
return false;
},
handleDOMEvents: {
mousemove: (view, event) => {
const pluginState = key.getState(view.state);
+2 -1
View File
@@ -1,5 +1,5 @@
// plane types
import { TSearchEntities } from "@plane/types";
import { IUserLite, TSearchEntities } from "@plane/types";
export type TMentionSuggestion = {
entity_identifier: string;
@@ -20,6 +20,7 @@ export type TMentionComponentProps = Pick<TMentionSuggestion, "entity_identifier
export type TReadOnlyMentionHandler = {
renderComponent: (props: TMentionComponentProps) => React.ReactNode;
getMentionedEntityDetails?: (entity_identifier: string) => { display_name: string } | undefined;
};
export type TMentionHandler = TReadOnlyMentionHandler & {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@plane/eslint-config",
"private": true,
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"files": [
"library.js",
-3
View File
@@ -3,7 +3,4 @@ module.exports = {
root: true,
extends: ["@plane/eslint-config/library.js"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: true,
},
};
+2 -9
View File
@@ -1,17 +1,11 @@
{
"name": "@plane/hooks",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"description": "React hooks that are shared across multiple apps internally",
"private": true,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist/**"
],
"main": "./src/index.ts",
"scripts": {
"build": "tsup ./src/index.ts --format esm,cjs --dts --external react --minify",
"lint": "eslint src --ext .ts,.tsx",
"lint:errors": "eslint src --ext .ts,.tsx --quiet"
},
@@ -22,7 +16,6 @@
"@plane/eslint-config": "*",
"@types/node": "^22.5.4",
"@types/react": "^18.3.11",
"tsup": "^8.4.0",
"typescript": "^5.3.3"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/i18n",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"description": "I18n shared across multiple apps internally",
"private": true,
+1 -1
View File
@@ -9,7 +9,7 @@
"workspace": "Workspace",
"views": "Views",
"analytics": "Analytics",
"work_items": "Work Items",
"work_items": "Work items",
"cycles": "Cycles",
"modules": "Modules",
"intake": "Intake",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/logger",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"description": "Logger shared across multiple apps internally",
"private": true,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/propel",
"version": "0.25.2",
"version": "0.25.3",
"private": true,
"license": "AGPL-3.0",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/services",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"private": true,
"main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/shared-state",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"description": "Shared state shared across multiple apps internally",
"private": true,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/tailwind-config",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"description": "common tailwind configuration across monorepo",
"main": "tailwind.config.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/types",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"private": true,
"types": "./src/index.d.ts",
+6
View File
@@ -6,6 +6,12 @@ export enum EUserPermissions {
export type TUserPermissions = EUserPermissions.ADMIN | EUserPermissions.MEMBER | EUserPermissions.GUEST;
// project network
export enum EProjectNetwork {
PRIVATE = 0,
PUBLIC = 2,
}
// project pages
export enum EPageAccess {
PUBLIC = 0,
+2 -1
View File
@@ -21,7 +21,8 @@ export type TInstanceGoogleAuthenticationConfigurationKeys =
export type TInstanceGithubAuthenticationConfigurationKeys =
| "GITHUB_CLIENT_ID"
| "GITHUB_CLIENT_SECRET";
| "GITHUB_CLIENT_SECRET"
| "GITHUB_ORGANIZATION_ID";
export type TInstanceGitlabAuthenticationConfigurationKeys =
| "GITLAB_HOST"
+1 -1
View File
@@ -27,6 +27,7 @@ export interface IPartialProject {
inbox_view: boolean;
guest_view_all_features?: boolean;
project_lead?: IUserLite | string | null;
network?: number;
// Timestamps
created_at?: Date;
updated_at?: Date;
@@ -50,7 +51,6 @@ export interface IProject extends IPartialProject {
anchor?: string | null;
is_favorite?: boolean;
members?: string[];
network?: number;
timezone?: string;
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@plane/typescript-config",
"version": "0.25.2",
"version": "0.25.3",
"license": "AGPL-3.0",
"private": true,
"files": [
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@plane/ui",
"description": "UI components shared across multiple apps internally",
"private": true,
"version": "0.25.2",
"version": "0.25.3",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
+2 -9
View File
@@ -1,17 +1,11 @@
{
"name": "@plane/utils",
"version": "0.25.2",
"version": "0.25.3",
"description": "Helper functions shared across multiple apps internally",
"license": "AGPL-3.0",
"private": true,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist/**"
],
"main": "./src/index.ts",
"scripts": {
"build": "tsup ./src/index.ts --format esm,cjs --dts --external react --minify",
"lint": "eslint src --ext .ts,.tsx",
"lint:errors": "eslint src --ext .ts,.tsx --quiet"
},
@@ -29,7 +23,6 @@
"@types/node": "^22.5.4",
"@types/react": "^18.3.11",
"@types/zxcvbn": "^4.4.5",
"tsup": "^8.4.0",
"typescript": "^5.3.3"
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"extends": "@plane/typescript-config/react-library.json",
"extends": "@plane/typescript-config/base.json",
"compilerOptions": {
"jsx": "react",
"lib": ["esnext", "dom"]
@@ -21,7 +21,7 @@ export const OAuthOptions: React.FC = observer(() => {
<GoogleOAuthButton text="Sign in with Google" />
</div>
)}
{config?.is_github_enabled && <GithubOAuthButton text="Sign in with Github" />}
{config?.is_github_enabled && <GithubOAuthButton text="Sign in with GitHub" />}
{config?.is_gitlab_enabled && <GitlabOAuthButton text="Sign in with GitLab" />}
</div>
</>
@@ -7,6 +7,8 @@ import { EditorMentionsRoot } from "@/components/editor";
// helpers
import { cn } from "@/helpers/common.helper";
import { getReadOnlyEditorFileHandlers } from "@/helpers/editor.helper";
// store hooks
import { useMember } from "@/hooks/store";
type LiteTextReadOnlyEditorWrapperProps = MakeOptional<
Omit<ILiteTextReadOnlyEditor, "fileHandler" | "mentionHandler">,
@@ -17,22 +19,29 @@ type LiteTextReadOnlyEditorWrapperProps = MakeOptional<
};
export const LiteTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, LiteTextReadOnlyEditorWrapperProps>(
({ anchor, workspaceId, disabledExtensions, ...props }, ref) => (
<LiteTextReadOnlyEditorWithRef
ref={ref}
disabledExtensions={disabledExtensions ?? []}
fileHandler={getReadOnlyEditorFileHandlers({
anchor,
workspaceId,
})}
mentionHandler={{
renderComponent: (props) => <EditorMentionsRoot {...props} />,
}}
{...props}
// overriding the customClassName to add relative class passed
containerClassName={cn(props.containerClassName, "relative p-2")}
/>
)
({ anchor, workspaceId, disabledExtensions, ...props }, ref) => {
const { getMemberById } = useMember();
return (
<LiteTextReadOnlyEditorWithRef
ref={ref}
disabledExtensions={disabledExtensions ?? []}
fileHandler={getReadOnlyEditorFileHandlers({
anchor,
workspaceId,
})}
mentionHandler={{
renderComponent: (props) => <EditorMentionsRoot {...props} />,
getMentionedEntityDetails: (id: string) => ({
display_name: getMemberById(id)?.member__display_name ?? "",
}),
}}
{...props}
// overriding the customClassName to add relative class passed
containerClassName={cn(props.containerClassName, "relative p-2")}
/>
);
}
);
LiteTextReadOnlyEditor.displayName = "LiteTextReadOnlyEditor";
@@ -6,6 +6,8 @@ import { MakeOptional } from "@plane/types";
import { EditorMentionsRoot } from "@/components/editor";
// helpers
import { getEditorFileHandlers } from "@/helpers/editor.helper";
// store hooks
import { useMember } from "@/hooks/store";
interface RichTextEditorWrapperProps
extends MakeOptional<Omit<IRichTextEditor, "fileHandler" | "mentionHandler">, "disabledExtensions"> {
@@ -16,11 +18,14 @@ interface RichTextEditorWrapperProps
export const RichTextEditor = forwardRef<EditorRefApi, RichTextEditorWrapperProps>((props, ref) => {
const { anchor, containerClassName, uploadFile, workspaceId, disabledExtensions, ...rest } = props;
const { getMemberById } = useMember();
return (
<RichTextEditorWithRef
mentionHandler={{
renderComponent: (props) => <EditorMentionsRoot {...props} />,
getMentionedEntityDetails: (id: string) => ({
display_name: getMemberById(id)?.member__display_name ?? "",
}),
}}
ref={ref}
disabledExtensions={disabledExtensions ?? []}
@@ -7,6 +7,8 @@ import { EditorMentionsRoot } from "@/components/editor";
// helpers
import { cn } from "@/helpers/common.helper";
import { getReadOnlyEditorFileHandlers } from "@/helpers/editor.helper";
// store hooks
import { useMember } from "@/hooks/store";
type RichTextReadOnlyEditorWrapperProps = MakeOptional<
Omit<IRichTextReadOnlyEditor, "fileHandler" | "mentionHandler">,
@@ -17,22 +19,29 @@ type RichTextReadOnlyEditorWrapperProps = MakeOptional<
};
export const RichTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, RichTextReadOnlyEditorWrapperProps>(
({ anchor, workspaceId, disabledExtensions, ...props }, ref) => (
<RichTextReadOnlyEditorWithRef
ref={ref}
disabledExtensions={disabledExtensions ?? []}
fileHandler={getReadOnlyEditorFileHandlers({
anchor,
workspaceId,
})}
mentionHandler={{
renderComponent: (props) => <EditorMentionsRoot {...props} />,
}}
{...props}
// overriding the customClassName to add relative class passed
containerClassName={cn("relative p-0 border-none", props.containerClassName)}
/>
)
({ anchor, workspaceId, disabledExtensions, ...props }, ref) => {
const { getMemberById } = useMember();
return (
<RichTextReadOnlyEditorWithRef
ref={ref}
disabledExtensions={disabledExtensions ?? []}
fileHandler={getReadOnlyEditorFileHandlers({
anchor,
workspaceId,
})}
mentionHandler={{
renderComponent: (props) => <EditorMentionsRoot {...props} />,
getMentionedEntityDetails: (id: string) => ({
display_name: getMemberById(id)?.member__display_name ?? "",
}),
}}
{...props}
// overriding the customClassName to add relative class passed
containerClassName={cn("relative p-0 border-none", props.containerClassName)}
/>
);
}
);
RichTextReadOnlyEditor.displayName = "RichTextReadOnlyEditor";
-11
View File
@@ -1,11 +0,0 @@
// google.d.ts
interface GsiButtonConfiguration {
type: "standard" | "icon";
theme?: "outline" | "filled_blue" | "filled_black";
size?: "large" | "medium" | "small";
text?: "signin_with" | "signup_with" | "continue_with" | "signup_with";
shape?: "rectangular" | "pill" | "circle" | "square";
logo_alignment?: "left" | "center";
width?: number;
local?: string;
}
-9
View File
@@ -1,9 +0,0 @@
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./sentry.server.config');
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('./sentry.edge.config');
}
}
+14 -44
View File
@@ -1,8 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
/* eslint-disable @typescript-eslint/no-var-requires */
/** @type {import('next').NextConfig} */
require("dotenv").config({ path: ".env" });
const { withSentryConfig } = require("@sentry/nextjs");
const nextConfig = {
trailingSlash: true,
@@ -27,45 +23,19 @@ const nextConfig = {
],
unoptimized: true,
},
transpilePackages: [
"@plane/constants",
"@plane/editor",
"@plane/hooks",
"@plane/i18n",
"@plane/logger",
"@plane/propel",
"@plane/services",
"@plane/shared-state",
"@plane/types",
"@plane/ui",
"@plane/utils",
],
};
const sentryConfig = {
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options
org: process.env.SENTRY_ORG_ID || "plane-hq",
project: process.env.SENTRY_PROJECT_ID || "plane-space",
authToken: process.env.SENTRY_AUTH_TOKEN,
// Only print logs for uploading source maps in CI
silent: true,
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
// This can increase your server load as well as your hosting bill.
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
// side errors will fail.
tunnelRoute: "/monitoring",
// Hides source maps from generated client bundles
hideSourceMaps: true,
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: true,
};
if (parseInt(process.env.SENTRY_MONITORING_ENABLED || "0", 10)) {
module.exports = withSentryConfig(nextConfig, sentryConfig);
} else {
module.exports = nextConfig;
}
module.exports = nextConfig;
+2 -3
View File
@@ -1,6 +1,6 @@
{
"name": "space",
"version": "0.25.2",
"version": "0.25.3",
"private": true,
"license": "AGPL-3.0",
"scripts": {
@@ -25,7 +25,6 @@
"@plane/types": "*",
"@plane/ui": "*",
"@plane/services": "*",
"@sentry/nextjs": "^8.54.0",
"axios": "^1.8.3",
"clsx": "^2.0.0",
"date-fns": "^4.1.0",
@@ -37,7 +36,7 @@
"mobx": "^6.10.0",
"mobx-react": "^9.1.1",
"mobx-utils": "^6.0.8",
"next": "^14.2.20",
"next": "^14.2.25",
"next-themes": "^0.2.1",
"nprogress": "^0.2.0",
"react": "^18.3.1",
-31
View File
@@ -1,31 +0,0 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || "development",
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
replaysOnErrorSampleRate: 1.0,
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
replaysSessionSampleRate: 0.1,
// You can remove this option if you're not planning to use the Sentry Session Replay feature:
integrations: [
Sentry.replayIntegration({
// Additional Replay configuration goes in here, for example:
maskAllText: true,
blockAllMedia: true,
}),
],
});
-17
View File
@@ -1,17 +0,0 @@
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
// The config you add here will be used whenever one of the edge features is loaded.
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || "development",
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
-3
View File
@@ -1,3 +0,0 @@
defaults.url=https://sentry.io/
defaults.org=plane
defaults.project=plane-space
-19
View File
@@ -1,19 +0,0 @@
// This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || "development",
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
// Uncomment the line below to enable Spotlight (https://spotlightjs.com)
// spotlight: process.env.NODE_ENV === 'development',
});
+1 -7
View File
@@ -18,13 +18,7 @@
"NEXT_PUBLIC_POSTHOG_KEY",
"NEXT_PUBLIC_POSTHOG_HOST",
"NEXT_PUBLIC_POSTHOG_DEBUG",
"NEXT_PUBLIC_SUPPORT_EMAIL",
"SENTRY_AUTH_TOKEN",
"SENTRY_ORG_ID",
"SENTRY_PROJECT_ID",
"NEXT_PUBLIC_SENTRY_ENVIRONMENT",
"NEXT_PUBLIC_SENTRY_DSN",
"SENTRY_MONITORING_ENABLED"
"NEXT_PUBLIC_SUPPORT_EMAIL"
],
"tasks": {
"build": {
@@ -15,10 +15,12 @@ const MembersSettingsPage = observer(() => {
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
// derived values
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Members` : undefined;
const canPerformProjectMemberActions = allowPermissions(
const isProjectMemberOrAdmin = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.PROJECT
);
const isWorkspaceAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
const canPerformProjectMemberActions = isProjectMemberOrAdmin || isWorkspaceAdmin;
if (workspaceUserInfo && !canPerformProjectMemberActions) {
return <NotAuthorizedView section="settings" isProjectView />;
@@ -3,7 +3,8 @@
import { FC, ReactNode } from "react";
import { observer } from "mobx-react";
// components
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useParams, usePathname } from "next/navigation";
import { EUserWorkspaceRoles, WORKSPACE_SETTINGS_ACCESS } from "@plane/constants";
import { NotAuthorizedView } from "@/components/auth-screens";
import { AppHeader } from "@/components/core";
// hooks
@@ -21,17 +22,26 @@ export interface IWorkspaceSettingLayout {
const WorkspaceSettingLayout: FC<IWorkspaceSettingLayout> = observer((props) => {
const { children } = props;
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const { workspaceUserInfo } = useUserPermissions();
const pathname = usePathname();
const [workspaceSlug, suffix, route] = pathname.replace(/^\/|\/$/g, "").split("/"); // Regex removes leading and trailing slashes
// derived values
const isWorkspaceAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
const userWorkspaceRole = workspaceUserInfo?.[workspaceSlug.toString()]?.role;
const isAuthorized =
pathname &&
workspaceSlug &&
userWorkspaceRole &&
WORKSPACE_SETTINGS_ACCESS[route ? `/${suffix}/${route}` : `/${suffix}`]?.includes(
userWorkspaceRole as EUserWorkspaceRoles
);
return (
<>
<AppHeader header={<WorkspaceSettingHeader />} />
<MobileWorkspaceSettingsTabs />
<div className="inset-y-0 flex flex-row vertical-scrollbar scrollbar-lg h-full w-full overflow-y-auto">
{workspaceUserInfo && !isWorkspaceAdmin ? (
{workspaceUserInfo && !isAuthorized ? (
<NotAuthorizedView section="settings" />
) : (
<>
+8 -8
View File
@@ -9,13 +9,13 @@ import {
ChevronLeft,
LogOut,
MoveLeft,
Plus,
UserPlus,
Activity,
Bell,
CircleUser,
KeyRound,
Settings2,
CirclePlus,
Mails,
} from "lucide-react";
// plane imports
import { PROFILE_ACTION_LINKS } from "@plane/constants";
@@ -35,14 +35,14 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
const WORKSPACE_ACTION_LINKS = [
{
key: "create_workspace",
Icon: Plus,
label: "Create workspace",
Icon: CirclePlus,
i18n_label: "create_workspace",
href: "/create-workspace",
},
{
key: "invitations",
Icon: UserPlus,
label: "Invitations",
Icon: Mails,
i18n_label: "workspace_invites",
href: "/invitations",
},
];
@@ -243,8 +243,8 @@ export const ProfileLayoutSidebar = observer(() => {
sidebarCollapsed ? "justify-center" : ""
}`}
>
{<link.Icon className="h-4 w-4" />}
{!sidebarCollapsed && t(link.key)}
{<link.Icon className="flex-shrink-0 size-4" />}
{!sidebarCollapsed && t(link.i18n_label)}
</div>
</Tooltip>
</Link>
@@ -0,0 +1,10 @@
import { FC } from "react";
export type TWorkItemAdditionalWidgets = {
workspaceSlug: string;
projectId: string;
workItemId: string;
disabled: boolean;
};
export const WorkItemAdditionalWidgets: FC<TWorkItemAdditionalWidgets> = (props) => <></>;
@@ -0,0 +1 @@
export * from "./additional-widgets";
+1 -1
View File
@@ -18,7 +18,7 @@ export const PROJECT_SETTINGS = {
key: "members",
i18n_label: "members",
href: `/settings/members`,
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`,
Icon: SettingIcon,
},
@@ -0,0 +1,4 @@
import { TIssue } from "@plane/types";
import { getIssueIds } from "@/store/issue/helpers/base-issues-utils";
export const workItemSortWithOrderByExtended = (array: TIssue[], key?: string) => getIssueIds(array);
@@ -29,7 +29,7 @@ export const OAuthOptions: React.FC<TOAuthOptionProps> = observer(() => {
<GoogleOAuthButton text="Continue with Google" />
</div>
)}
{config?.is_github_enabled && <GithubOAuthButton text="Continue with Github" />}
{config?.is_github_enabled && <GithubOAuthButton text="Continue with GitHub" />}
{config?.is_gitlab_enabled && <GitlabOAuthButton text="Continue with GitLab" />}
</div>
</>
@@ -149,7 +149,7 @@ export const ImagePickerPopover: React.FC<Props> = observer((props) => {
useOutsideClickDetector(ref, handleClose);
return (
<Popover className="relative z-20" ref={ref} tabIndex={tabIndex} onKeyDown={handleKeyDown}>
<Popover className="relative z-19" ref={ref} tabIndex={tabIndex} onKeyDown={handleKeyDown}>
<Popover.Button
className="rounded border border-custom-border-300 bg-custom-background-100 px-2 py-1 text-xs text-custom-text-200 hover:text-custom-text-100"
onClick={handleOnClick}
@@ -34,7 +34,13 @@ export const BulkDeleteIssuesModalItem: React.FC<Props> = observer((props: Props
backgroundColor: color,
}}
/>
<IssueIdentifier issueId={issue.id} projectId={issue.project_id} textContainerClassName="text-xs" />
<IssueIdentifier
projectId={issue.project_id}
issueTypeId={issue.type_id}
projectIdentifier={issue.project__identifier}
issueSequenceId={issue.sequence_id}
textContainerClassName="text-xs"
/>
<span>{issue.name}</span>
</div>
</Combobox.Option>
@@ -149,10 +149,10 @@ export const TransferIssuesModal: React.FC<Props> = observer((props) => {
}}
>
<ContrastIcon className="h-5 w-5" />
<div className="flex w-full justify-between">
<span>{cycleDetails?.name}</span>
<div className="flex w-full justify-between truncate">
<span className="truncate">{cycleDetails?.name}</span>
{cycleDetails.status && (
<span className=" flex items-center rounded-full bg-custom-background-80 px-2 capitalize">
<span className="flex-shrink-0 flex items-center rounded-full bg-custom-background-80 px-2 capitalize">
{cycleDetails.status.toLocaleLowerCase()}
</span>
)}
@@ -13,6 +13,8 @@ import { cn } from "@/helpers/common.helper";
import { isCommentEmpty } from "@/helpers/string.helper";
// hooks
import { useEditorConfig, useEditorMention } from "@/hooks/editor";
// store hooks
import { useMember } from "@/hooks/store";
// plane web hooks
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
// plane web services
@@ -57,6 +59,8 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
const [isFocused, setIsFocused] = useState(showToolbarInitially);
// editor flaggings
const { liteTextEditor: disabledExtensions } = useEditorFlagging(workspaceSlug?.toString());
// store hooks
const { getUserDetails } = useMember();
// use editor mention
const { fetchMentions } = useEditorMention({
searchEntity: async (payload) =>
@@ -97,6 +101,7 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
return res;
},
renderComponent: (props) => <EditorMentionsRoot {...props} />,
getMentionedEntityDetails: (id: string) => ({ display_name: getUserDetails(id)?.display_name ?? "" }),
}}
placeholder={placeholder}
containerClassName={cn(containerClassName, "relative")}
@@ -8,6 +8,8 @@ import { EditorMentionsRoot } from "@/components/editor";
import { cn } from "@/helpers/common.helper";
// hooks
import { useEditorConfig } from "@/hooks/editor";
// store hooks
import { useMember } from "@/hooks/store";
// plane web hooks
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
@@ -22,6 +24,9 @@ type LiteTextReadOnlyEditorWrapperProps = MakeOptional<
export const LiteTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, LiteTextReadOnlyEditorWrapperProps>(
({ workspaceId, workspaceSlug, projectId, disabledExtensions: additionalDisabledExtensions, ...props }, ref) => {
// store hooks
const { getUserDetails } = useMember();
// editor flaggings
const { liteTextEditor: disabledExtensions } = useEditorFlagging(workspaceSlug?.toString());
// editor config
@@ -38,6 +43,7 @@ export const LiteTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, Lit
})}
mentionHandler={{
renderComponent: (props) => <EditorMentionsRoot {...props} />,
getMentionedEntityDetails: (id: string) => ({ display_name: getUserDetails(id)?.display_name ?? "" }),
}}
{...props}
// overriding the containerClassName to add relative class passed
@@ -8,6 +8,8 @@ import { EditorMentionsRoot } from "@/components/editor";
import { cn } from "@/helpers/common.helper";
// hooks
import { useEditorConfig, useEditorMention } from "@/hooks/editor";
// store hooks
import { useMember } from "@/hooks/store";
// plane web hooks
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
@@ -31,6 +33,8 @@ export const RichTextEditor = forwardRef<EditorRefApi, RichTextEditorWrapperProp
disabledExtensions: additionalDisabledExtensions,
...rest
} = props;
// store hooks
const { getUserDetails } = useMember();
// editor flaggings
const { richTextEditor: disabledExtensions } = useEditorFlagging(workspaceSlug?.toString());
// use editor mention
@@ -57,6 +61,7 @@ export const RichTextEditor = forwardRef<EditorRefApi, RichTextEditorWrapperProp
return res;
},
renderComponent: (props) => <EditorMentionsRoot {...props} />,
getMentionedEntityDetails: (id: string) => ({ display_name: getUserDetails(id)?.display_name ?? "" }),
}}
{...rest}
containerClassName={cn("relative pl-3 pb-3", containerClassName)}
@@ -8,6 +8,8 @@ import { EditorMentionsRoot } from "@/components/editor";
import { cn } from "@/helpers/common.helper";
// hooks
import { useEditorConfig } from "@/hooks/editor";
// store hooks
import { useMember } from "@/hooks/store";
// plane web hooks
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
@@ -22,6 +24,9 @@ type RichTextReadOnlyEditorWrapperProps = MakeOptional<
export const RichTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, RichTextReadOnlyEditorWrapperProps>(
({ workspaceId, workspaceSlug, projectId, disabledExtensions: additionalDisabledExtensions, ...props }, ref) => {
// store hooks
const { getUserDetails } = useMember();
// editor flaggings
const { richTextEditor: disabledExtensions } = useEditorFlagging(workspaceSlug?.toString());
// editor config
@@ -38,6 +43,7 @@ export const RichTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, Ric
})}
mentionHandler={{
renderComponent: (props) => <EditorMentionsRoot {...props} />,
getMentionedEntityDetails: (id: string) => ({ display_name: getUserDetails(id)?.display_name ?? "" }),
}}
{...props}
// overriding the containerClassName to add relative class passed
@@ -29,20 +29,21 @@ export const WidgetList = observer(({ workspaceSlug }: { workspaceSlug: string }
if (!sourceData.id) return;
if (droppedId) {
try {
reorderWidget(workspaceSlug, sourceData.id, droppedId, instruction); /** sequence */
setToast({
type: TOAST_TYPE.SUCCESS,
title: t("toast.success"),
message: t("home.widget.reordered_successfully"),
reorderWidget(workspaceSlug, sourceData.id, droppedId, instruction)
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: t("toast.success"),
message: t("home.widget.reordered_successfully"),
});
})
.catch(() => {
setToast({
type: TOAST_TYPE.ERROR,
title: t("toast.error"),
message: t("home.widget.reordering_failed"),
});
});
} catch {
setToast({
type: TOAST_TYPE.ERROR,
title: t("toast.error"),
message: t("home.widget.reordering_failed"),
});
}
}
};
@@ -11,6 +11,7 @@ import {
// hooks
import { useIssueDetail } from "@/hooks/store";
// Plane-web
import { WorkItemAdditionalWidgets } from "@/plane-web/components/issues/issue-detail-widgets";
import { useTimeLineRelationOptions } from "@/plane-web/components/relations";
type Props = {
@@ -68,6 +69,13 @@ export const IssueDetailWidgetCollapsibles: FC<Props> = observer((props) => {
disabled={disabled}
/>
)}
<WorkItemAdditionalWidgets
workspaceSlug={workspaceSlug}
projectId={projectId}
workItemId={issueId}
disabled={disabled}
/>
</div>
);
});
@@ -299,7 +299,6 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
projectId={projectId}
workspaceSlug={workspaceSlug}
isEditable={isEditable}
isPeekView
/>
</div>
</div>
@@ -12,7 +12,7 @@ type Props = {
searchQuery: string;
};
export const FilterTargetDate: React.FC<Props> = observer((props) => {
export const FilterDueDate: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, searchQuery } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
@@ -46,7 +46,7 @@ export const FilterTargetDate: React.FC<Props> = observer((props) => {
/>
)}
<FilterHeader
title={`Target date${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
title={`Due date${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
@@ -18,13 +18,13 @@ import {
FilterAssignees,
FilterMentions,
FilterCreatedBy,
FilterDueDate,
FilterLabels,
FilterPriority,
FilterProjects,
FilterStartDate,
FilterState,
FilterStateGroup,
FilterTargetDate,
FilterCycle,
FilterModule,
FilterIssueGrouping,
@@ -276,7 +276,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
{/* target_date */}
{isFilterEnabled("target_date") && (
<div className="py-2">
<FilterTargetDate
<FilterDueDate
appliedFilters={filters.target_date ?? null}
handleUpdate={(val) => handleFiltersUpdate("target_date", val)}
searchQuery={filtersSearchQuery}
@@ -1,6 +1,7 @@
export * from "./assignee";
export * from "./mentions";
export * from "./created-by";
export * from "./due-date";
export * from "./filters-selection";
export * from "./labels";
export * from "./priority";
@@ -10,4 +11,3 @@ export * from "./state-group";
export * from "./state";
export * from "./cycle";
export * from "./module";
export * from "./target-date";
@@ -261,19 +261,21 @@ const IssueRowDetails = observer((props: IssueRowDetailsProps) => {
<ControlLink
href={workItemLink}
onClick={() => handleIssuePeekOverview(issueDetail)}
className={cn(
"group clickable cursor-pointer h-11 w-[28rem] flex items-center text-sm after:absolute border-r-[0.5px] z-10 border-custom-border-200 bg-transparent group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10",
{
"border-b-[0.5px]": !getIsIssuePeeked(issueDetail.id),
"border border-custom-primary-70 hover:border-custom-primary-70":
getIsIssuePeeked(issueDetail.id) && nestingLevel === peekIssue?.nestingLevel,
"shadow-[8px_22px_22px_10px_rgba(0,0,0,0.05)]": isScrolled.current,
}
)}
className="outline-none"
disabled={!!issueDetail?.tempId}
>
<Row className="flex item-center flex-row w-full">
<div className="flex items-center gap-0.5 min-w-min py-2.5">
<Row
className={cn(
"group clickable cursor-pointer h-11 w-[28rem] flex items-center text-sm after:absolute border-r-[0.5px] z-10 border-custom-border-200 bg-transparent group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10",
{
"border-b-[0.5px]": !getIsIssuePeeked(issueDetail.id),
"border border-custom-primary-70 hover:border-custom-primary-70":
getIsIssuePeeked(issueDetail.id) && nestingLevel === peekIssue?.nestingLevel,
"shadow-[8px_22px_22px_10px_rgba(0,0,0,0.05)]": isScrolled.current,
}
)}
>
<div className="flex items-center gap-0.5 min-w-min py-2">
{/* select checkbox */}
{projectId && canSelectIssues && (
<Tooltip

Some files were not shown because too many files have changed in this diff Show More