Compare commits

..

24 Commits

Author SHA1 Message Date
Palanikannan M 2f76d6f4a5 chore: fix tsup vulnerability 2025-05-28 02:14:10 +05:30
Palanikannan M 8c80ace344 chore: add constants for editor meta as well 2025-05-27 23:46:26 +05:30
Palanikannan M 7fcb7981ae fix: collaborative editor editable value 2025-05-27 23:43:06 +05:30
Palanikannan M 46b951ca61 fix: image extension names 2025-05-27 23:27:12 +05:30
Palanikannan M 5b067d3a1b fix: iseditable is hard coded 2025-05-27 23:02:15 +05:30
Aaryan Khandelwal dbca7d3bf3 fix: file plugin object reference 2025-05-27 21:04:29 +05:30
Aaryan Khandelwal e9d69ff1ac Merge branch 'preview' of https://github.com/makeplane/plane into refactor/file-plugins 2025-05-27 20:30:12 +05:30
Palanikannan M c44a2096eb fix: image upload types and heading types 2025-05-27 20:23:57 +05:30
Palanikannan M 5491e320c3 fix: tsup reloading issue 2025-05-27 19:12:07 +05:30
Aaryan Khandelwal 394621116b refactor: rename extension files 2025-05-22 16:50:41 +05:30
Aaryan Khandelwal 2ec62b2f83 fix: errors and warnings 2025-05-22 15:39:31 +05:30
Aaryan Khandelwal 90c51c793c chore: use extension enums 2025-05-22 15:09:28 +05:30
Aaryan Khandelwal 017f114c73 refactor: work item embed extension 2025-05-22 13:59:10 +05:30
Aaryan Khandelwal 8725f20d38 Merge branch 'preview' of https://github.com/makeplane/plane into refactor/file-plugins 2025-05-22 13:51:45 +05:30
Palanikannan M e059bccfba chore: refactoring out onCreate into a common utility 2025-05-21 22:45:27 +05:30
Aaryan Khandelwal d9a010396d chore: remove standalone plugin extensions 2025-05-21 19:15:19 +05:30
Aaryan Khandelwal 8711669142 refactor: file plugins 2025-05-21 18:51:25 +05:30
Aaryan Khandelwal 4b3cc93fe4 refactor: utility extension 2025-05-21 18:31:44 +05:30
Aaryan Khandelwal 2223f822fa fix: build errors 2025-05-19 13:39:40 +05:30
Aaryan Khandelwal 27de6f217c chore: extension fileset storage key 2025-05-19 13:33:53 +05:30
Aaryan Khandelwal 07bc613992 chore: update meta tag name 2025-05-19 13:24:22 +05:30
Aaryan Khandelwal c042421344 refactor: image extension storage types 2025-05-19 13:18:54 +05:30
Aaryan Khandelwal fdadf91f89 Merge branch 'preview' of https://github.com/makeplane/plane into refactor/file-plugins 2025-05-19 13:14:04 +05:30
Aaryan Khandelwal 16151e99d1 refactor: file plugins and types 2025-05-16 19:13:33 +05:30
265 changed files with 1897 additions and 5151 deletions
+1 -2
View File
@@ -2,7 +2,6 @@
*.pyc
.env
venv
.venv
node_modules/
**/node_modules/
npm-debug.log
@@ -15,4 +14,4 @@ build/
out/
**/out/
dist/
**/dist/
**/dist/
+1 -1
View File
@@ -31,7 +31,7 @@
"lucide-react": "^0.469.0",
"mobx": "^6.12.0",
"mobx-react": "^9.1.1",
"next": "^14.2.29",
"next": "^14.2.28",
"next-themes": "^0.2.1",
"postcss": "^8.4.38",
"react": "^18.3.1",
+2 -5
View File
@@ -148,13 +148,10 @@ class ProjectMemberAdminSerializer(BaseSerializer):
fields = "__all__"
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
original_role = serializers.IntegerField(source='role', read_only=True)
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
class Meta:
model = ProjectMember
fields = ("id", "role", "member", "project", "original_role", "created_at")
read_only_fields = ["original_role", "created_at"]
fields = ("id", "role", "member", "project")
class ProjectMemberInviteSerializer(BaseSerializer):
-16
View File
@@ -3,22 +3,11 @@ from rest_framework import serializers
# Module import
from plane.db.models import Account, Profile, User, Workspace, WorkspaceMemberInvite
from plane.utils.url import contains_url
from .base import BaseSerializer
class UserSerializer(BaseSerializer):
def validate_first_name(self, value):
if contains_url(value):
raise serializers.ValidationError("First name cannot contain a URL.")
return value
def validate_last_name(self, value):
if contains_url(value):
raise serializers.ValidationError("Last name cannot contain a URL.")
return value
class Meta:
model = User
# Exclude password field from the serializer
@@ -110,16 +99,11 @@ class UserMeSettingsSerializer(BaseSerializer):
workspace_member__member=obj.id,
workspace_member__is_active=True,
).first()
logo_asset_url = workspace.logo_asset.asset_url if workspace.logo_asset is not None else ""
return {
"last_workspace_id": profile.last_workspace_id,
"last_workspace_slug": (
workspace.slug if workspace is not None else ""
),
"last_workspace_name": (
workspace.name if workspace is not None else ""
),
"last_workspace_logo": (logo_asset_url),
"fallback_workspace_id": profile.last_workspace_id,
"fallback_workspace_slug": (
workspace.slug if workspace is not None else ""
@@ -25,12 +25,10 @@ from plane.db.models import (
WorkspaceUserPreference,
)
from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS
from plane.utils.url import contains_url
# Django imports
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError
import re
class WorkSpaceSerializer(DynamicBaseSerializer):
@@ -38,21 +36,10 @@ class WorkSpaceSerializer(DynamicBaseSerializer):
logo_url = serializers.CharField(read_only=True)
role = serializers.IntegerField(read_only=True)
def validate_name(self, value):
# Check if the name contains a URL
if contains_url(value):
raise serializers.ValidationError("Name must not contain URLs")
return value
def validate_slug(self, value):
# Check if the slug is restricted
if value in RESTRICTED_WORKSPACE_SLUGS:
raise serializers.ValidationError("Slug is not valid")
# Slug should only contain alphanumeric characters, hyphens, and underscores
if not re.match(r"^[a-zA-Z0-9_-]+$", value):
raise serializers.ValidationError(
"Slug can only contain letters, numbers, hyphens (-), and underscores (_)"
)
return value
class Meta:
-7
View File
@@ -12,7 +12,6 @@ from plane.app.views import (
AssetRestoreEndpoint,
ProjectAssetEndpoint,
ProjectBulkAssetEndpoint,
AssetCheckEndpoint,
)
@@ -82,11 +81,5 @@ urlpatterns = [
path(
"assets/v2/workspaces/<str:slug>/projects/<uuid:project_id>/<uuid:entity_id>/bulk/",
ProjectBulkAssetEndpoint.as_view(),
name="bulk-asset-update",
),
path(
"assets/v2/workspaces/<str:slug>/check/<uuid:asset_id>/",
AssetCheckEndpoint.as_view(),
name="asset-check",
),
]
-1
View File
@@ -106,7 +106,6 @@ from .asset.v2 import (
AssetRestoreEndpoint,
ProjectAssetEndpoint,
ProjectBulkAssetEndpoint,
AssetCheckEndpoint,
)
from .issue.base import (
IssueListEndpoint,
-11
View File
@@ -707,14 +707,3 @@ class ProjectBulkAssetEndpoint(BaseAPIView):
pass
return Response(status=status.HTTP_204_NO_CONTENT)
class AssetCheckEndpoint(BaseAPIView):
"""Endpoint to check if an asset exists."""
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
def get(self, request, slug, asset_id):
asset = FileAsset.all_objects.filter(
id=asset_id, workspace__slug=slug, deleted_at__isnull=True
).exists()
return Response({"exists": asset}, status=status.HTTP_200_OK)
-16
View File
@@ -15,7 +15,6 @@ from plane.app.serializers import IssueLinkSerializer
from plane.app.permissions import ProjectEntityPermission
from plane.db.models import IssueLink
from plane.bgtasks.issue_activities_task import issue_activity
from plane.bgtasks.work_item_link_task import crawl_work_item_link_title
from plane.utils.host import base_host
@@ -45,9 +44,6 @@ class IssueLinkViewSet(BaseViewSet):
serializer = IssueLinkSerializer(data=request.data)
if serializer.is_valid():
serializer.save(project_id=project_id, issue_id=issue_id)
crawl_work_item_link_title(
serializer.data.get("id"), serializer.data.get("url")
)
issue_activity.delay(
type="link.activity.created",
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
@@ -59,10 +55,6 @@ class IssueLinkViewSet(BaseViewSet):
notification=True,
origin=base_host(request=request, is_app=True),
)
issue_link = self.get_queryset().get(id=serializer.data.get("id"))
serializer = IssueLinkSerializer(issue_link)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -74,14 +66,9 @@ class IssueLinkViewSet(BaseViewSet):
current_instance = json.dumps(
IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder
)
serializer = IssueLinkSerializer(issue_link, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
crawl_work_item_link_title(
serializer.data.get("id"), serializer.data.get("url")
)
issue_activity.delay(
type="link.activity.updated",
requested_data=requested_data,
@@ -93,9 +80,6 @@ class IssueLinkViewSet(BaseViewSet):
notification=True,
origin=base_host(request=request, is_app=True),
)
issue_link = self.get_queryset().get(id=serializer.data.get("id"))
serializer = IssueLinkSerializer(issue_link)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -3,7 +3,6 @@ import csv
import io
import os
from datetime import date
import uuid
from dateutil.relativedelta import relativedelta
from django.db import IntegrityError
@@ -36,7 +35,6 @@ from plane.db.models import (
Workspace,
WorkspaceMember,
WorkspaceTheme,
Profile
)
from plane.app.permissions import ROLE, allow_permission
from django.utils.decorators import method_decorator
@@ -45,7 +43,6 @@ from django.views.decorators.vary import vary_on_cookie
from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS
from plane.license.utils.instance_value import get_configuration_value
from plane.bgtasks.workspace_seed_task import workspace_seed
from plane.utils.url import contains_url
class WorkSpaceViewSet(BaseViewSet):
@@ -112,12 +109,6 @@ class WorkSpaceViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST,
)
if contains_url(name):
return Response(
{"error": "Name cannot contain a URL"},
status=status.HTTP_400_BAD_REQUEST,
)
if serializer.is_valid(raise_exception=True):
serializer.save(owner=request.user)
# Create Workspace member
@@ -159,19 +150,8 @@ class WorkSpaceViewSet(BaseViewSet):
def partial_update(self, request, *args, **kwargs):
return super().partial_update(request, *args, **kwargs)
def remove_last_workspace_ids_from_user_settings(self, id: uuid.UUID) -> None:
"""
Remove the last workspace id from the user settings
"""
Profile.objects.filter(last_workspace_id=id).update(last_workspace_id=None)
return
@allow_permission([ROLE.ADMIN], level="WORKSPACE")
def destroy(self, request, *args, **kwargs):
# Get the workspace
workspace = self.get_object()
self.remove_last_workspace_ids_from_user_settings(workspace.id)
return super().destroy(request, *args, **kwargs)
@@ -1,185 +0,0 @@
# Python imports
import logging
# Third party imports
from celery import shared_task
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse, urljoin
import base64
import ipaddress
from typing import Dict, Any
from typing import Optional
from plane.db.models import IssueLink
from plane.utils.exception_logger import log_exception
logger = logging.getLogger("plane.worker")
DEFAULT_FAVICON = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWxpbmstaWNvbiBsdWNpZGUtbGluayI+PHBhdGggZD0iTTEwIDEzYTUgNSAwIDAgMCA3LjU0LjU0bDMtM2E1IDUgMCAwIDAtNy4wNy03LjA3bC0xLjcyIDEuNzEiLz48cGF0aCBkPSJNMTQgMTFhNSA1IDAgMCAwLTcuNTQtLjU0bC0zIDNhNSA1IDAgMCAwIDcuMDcgNy4wN2wxLjcxLTEuNzEiLz48L3N2Zz4=" # noqa: E501
@shared_task
def crawl_work_item_link_title(id: str, url: str) -> None:
meta_data = crawl_work_item_link_title_and_favicon(url)
issue_link = IssueLink.objects.get(id=id)
issue_link.metadata = meta_data
issue_link.save()
def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]:
"""
Crawls a URL to extract the title and favicon.
Args:
url (str): The URL to crawl
Returns:
str: JSON string containing title and base64-encoded favicon
"""
try:
# Prevent access to private IP ranges
parsed = urlparse(url)
try:
ip = ipaddress.ip_address(parsed.hostname)
if ip.is_private or ip.is_loopback or ip.is_reserved:
raise ValueError("Access to private/internal networks is not allowed")
except ValueError:
# Not an IP address, continue with domain validation
pass
# Set up headers to mimic a real browser
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" # noqa: E501
}
# Fetch the main page
response = requests.get(url, headers=headers, timeout=2)
response.raise_for_status()
# Parse HTML
soup = BeautifulSoup(response.content, "html.parser")
# Extract title
title_tag = soup.find("title")
title = title_tag.get_text().strip() if title_tag else None
# Fetch and encode favicon
favicon_base64 = fetch_and_encode_favicon(headers, soup, url)
# Prepare result
result = {
"title": title,
"favicon": favicon_base64["favicon_base64"],
"url": url,
"favicon_url": favicon_base64["favicon_url"],
}
return result
except requests.RequestException as e:
log_exception(e)
return {
"error": f"Request failed: {str(e)}",
"title": None,
"favicon": None,
"url": url,
}
except Exception as e:
log_exception(e)
return {
"error": f"Unexpected error: {str(e)}",
"title": None,
"favicon": None,
"url": url,
}
def find_favicon_url(soup: BeautifulSoup, base_url: str) -> Optional[str]:
"""
Find the favicon URL from HTML soup.
Args:
soup: BeautifulSoup object
base_url: Base URL for resolving relative paths
Returns:
str: Absolute URL to favicon or None
"""
# Look for various favicon link tags
favicon_selectors = [
'link[rel="icon"]',
'link[rel="shortcut icon"]',
'link[rel="apple-touch-icon"]',
'link[rel="apple-touch-icon-precomposed"]',
]
for selector in favicon_selectors:
favicon_tag = soup.select_one(selector)
if favicon_tag and favicon_tag.get("href"):
return urljoin(base_url, favicon_tag["href"])
# Fallback to /favicon.ico
parsed_url = urlparse(base_url)
fallback_url = f"{parsed_url.scheme}://{parsed_url.netloc}/favicon.ico"
# Check if fallback exists
try:
response = requests.head(fallback_url, timeout=2)
response.raise_for_status()
if response.status_code == 200:
return fallback_url
except requests.RequestException as e:
log_exception(e)
return None
return None
def fetch_and_encode_favicon(
headers: Dict[str, str], soup: BeautifulSoup, url: str
) -> Optional[Dict[str, str]]:
"""
Fetch favicon and encode it as base64.
Args:
favicon_url: URL to the favicon
headers: Request headers
Returns:
str: Base64 encoded favicon with data URI prefix or None
"""
try:
favicon_url = find_favicon_url(soup, url)
if favicon_url is None:
return {
"favicon_url": None,
"favicon_base64": f"data:image/svg+xml;base64,{DEFAULT_FAVICON}",
}
response = requests.get(favicon_url, headers=headers, timeout=2)
response.raise_for_status()
# Get content type
content_type = response.headers.get("content-type", "image/x-icon")
# Convert to base64
favicon_base64 = base64.b64encode(response.content).decode("utf-8")
# Return as data URI
return {
"favicon_url": favicon_url,
"favicon_base64": f"data:{content_type};base64,{favicon_base64}",
}
except Exception as e:
logger.warning(f"Failed to fetch favicon: {e}")
return {
"favicon_url": None,
"favicon_base64": f"data:image/svg+xml;base64,{DEFAULT_FAVICON}",
}
-8
View File
@@ -4,14 +4,6 @@ from typing import Optional
from urllib.parse import urlparse, urlunparse
def contains_url(value: str) -> bool:
"""
Check if the value contains a URL.
"""
url_pattern = re.compile(r"https?://|www\\.")
return bool(url_pattern.search(value))
def is_valid_url(url: str) -> bool:
"""
Validates whether the given string is a well-formed URL.
+1 -1
View File
@@ -57,7 +57,7 @@
"concurrently": "^9.0.1",
"nodemon": "^3.1.7",
"ts-node": "^10.9.2",
"tsup": "8.4.0",
"tsup": "^8.5.0",
"typescript": "5.3.3"
}
}
+1 -2
View File
@@ -30,8 +30,7 @@
"nanoid": "3.3.8",
"esbuild": "0.25.0",
"@babel/helpers": "7.26.10",
"@babel/runtime": "7.26.10",
"chokidar": "3.6.0"
"@babel/runtime": "7.26.10"
},
"packageManager": "yarn@1.22.22"
}
-1
View File
@@ -32,6 +32,5 @@ export * from "./dashboard";
export * from "./page";
export * from "./emoji";
export * from "./subscription";
export * from "./settings";
export * from "./icon";
export * from "./analytics-v2";
+30 -61
View File
@@ -1,53 +1,39 @@
export const PROFILE_SETTINGS = {
profile: {
key: "profile",
i18n_label: "profile.actions.profile",
href: `/settings/account`,
highlight: (pathname: string) => pathname === "/settings/account/",
},
security: {
key: "security",
i18n_label: "profile.actions.security",
href: `/settings/account/security`,
highlight: (pathname: string) => pathname === "/settings/account/security/",
},
activity: {
key: "activity",
i18n_label: "profile.actions.activity",
href: `/settings/account/activity`,
highlight: (pathname: string) => pathname === "/settings/account/activity/",
},
preferences: {
key: "preferences",
i18n_label: "profile.actions.preferences",
href: `/settings/account/preferences`,
highlight: (pathname: string) => pathname === "/settings/account/preferences",
},
notifications: {
key: "notifications",
i18n_label: "profile.actions.notifications",
href: `/settings/account/notifications`,
highlight: (pathname: string) => pathname === "/settings/account/notifications/",
},
"api-tokens": {
key: "api-tokens",
i18n_label: "profile.actions.api-tokens",
href: `/settings/account/api-tokens`,
highlight: (pathname: string) => pathname === "/settings/account/api-tokens/",
},
};
export const PROFILE_ACTION_LINKS: {
key: string;
i18n_label: string;
href: string;
highlight: (pathname: string) => boolean;
}[] = [
PROFILE_SETTINGS["profile"],
PROFILE_SETTINGS["security"],
PROFILE_SETTINGS["activity"],
PROFILE_SETTINGS["preferences"],
PROFILE_SETTINGS["notifications"],
PROFILE_SETTINGS["api-tokens"],
{
key: "profile",
i18n_label: "profile.actions.profile",
href: `/profile`,
highlight: (pathname: string) => pathname === "/profile/",
},
{
key: "security",
i18n_label: "profile.actions.security",
href: `/profile/security`,
highlight: (pathname: string) => pathname === "/profile/security/",
},
{
key: "activity",
i18n_label: "profile.actions.activity",
href: `/profile/activity`,
highlight: (pathname: string) => pathname === "/profile/activity/",
},
{
key: "appearance",
i18n_label: "profile.actions.appearance",
href: `/profile/appearance`,
highlight: (pathname: string) => pathname.includes("/profile/appearance"),
},
{
key: "notifications",
i18n_label: "profile.actions.notifications",
href: `/profile/notifications`,
highlight: (pathname: string) => pathname === "/profile/notifications/",
},
];
export const PROFILE_VIEWER_TAB = [
@@ -86,23 +72,6 @@ export const PROFILE_ADMINS_TAB = [
},
];
export const PREFERENCE_OPTIONS: {
id: string;
title: string;
description: string;
}[] = [
{
id: "theme",
title: "theme",
description: "select_or_customize_your_interface_color_scheme",
},
{
id: "start_of_week",
title: "First day of the week",
description: "This will change how all calendars in your app look.",
},
];
/**
* @description The start of the week for the user
* @enum {number}
-52
View File
@@ -1,52 +0,0 @@
import { PROFILE_SETTINGS } from ".";
import { WORKSPACE_SETTINGS } from "./workspace";
export enum WORKSPACE_SETTINGS_CATEGORY {
ADMINISTRATION = "administration",
FEATURES = "features",
DEVELOPER = "developer",
}
export enum PROFILE_SETTINGS_CATEGORY {
YOUR_PROFILE = "your profile",
DEVELOPER = "developer",
}
export enum PROJECT_SETTINGS_CATEGORY {
PROJECTS = "projects",
}
export const WORKSPACE_SETTINGS_CATEGORIES = [
WORKSPACE_SETTINGS_CATEGORY.ADMINISTRATION,
WORKSPACE_SETTINGS_CATEGORY.FEATURES,
WORKSPACE_SETTINGS_CATEGORY.DEVELOPER,
];
export const PROFILE_SETTINGS_CATEGORIES = [
PROFILE_SETTINGS_CATEGORY.YOUR_PROFILE,
PROFILE_SETTINGS_CATEGORY.DEVELOPER,
];
export const PROJECT_SETTINGS_CATEGORIES = [PROJECT_SETTINGS_CATEGORY.PROJECTS];
export const GROUPED_WORKSPACE_SETTINGS = {
[WORKSPACE_SETTINGS_CATEGORY.ADMINISTRATION]: [
WORKSPACE_SETTINGS["general"],
WORKSPACE_SETTINGS["members"],
WORKSPACE_SETTINGS["billing-and-plans"],
WORKSPACE_SETTINGS["export"],
],
[WORKSPACE_SETTINGS_CATEGORY.FEATURES]: [],
[WORKSPACE_SETTINGS_CATEGORY.DEVELOPER]: [WORKSPACE_SETTINGS["webhooks"]],
};
export const GROUPED_PROFILE_SETTINGS = {
[PROFILE_SETTINGS_CATEGORY.YOUR_PROFILE]: [
PROFILE_SETTINGS["profile"],
PROFILE_SETTINGS["preferences"],
PROFILE_SETTINGS["notifications"],
PROFILE_SETTINGS["security"],
PROFILE_SETTINGS["activity"],
],
[PROFILE_SETTINGS_CATEGORY.DEVELOPER]: [PROFILE_SETTINGS["api-tokens"]],
};
+8
View File
@@ -114,6 +114,13 @@ export const WORKSPACE_SETTINGS = {
access: [EUserWorkspaceRoles.ADMIN],
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/webhooks/`,
},
"api-tokens": {
key: "api-tokens",
i18n_label: "workspace_settings.settings.api_tokens.title",
href: `/settings/api-tokens`,
access: [EUserWorkspaceRoles.ADMIN],
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/api-tokens/`,
},
};
export const WORKSPACE_SETTINGS_ACCESS = Object.fromEntries(
@@ -132,6 +139,7 @@ export const WORKSPACE_SETTINGS_LINKS: {
WORKSPACE_SETTINGS["billing-and-plans"],
WORKSPACE_SETTINGS["export"],
WORKSPACE_SETTINGS["webhooks"],
WORKSPACE_SETTINGS["api-tokens"],
];
export const ROLE = {
+1 -1
View File
@@ -27,7 +27,7 @@
"@types/node": "^20.14.9",
"@types/reflect-metadata": "^0.1.0",
"@types/ws": "^8.5.10",
"tsup": "8.4.0",
"tsup": "^8.5.0",
"typescript": "^5.3.3"
},
"peerDependencies": {
+1 -1
View File
@@ -81,7 +81,7 @@
"@types/react": "^18.3.11",
"@types/react-dom": "^18.2.18",
"postcss": "^8.4.38",
"tsup": "8.4.0",
"tsup": "^8.5.0",
"typescript": "5.3.3"
},
"keywords": [
@@ -1,14 +0,0 @@
import { Editor } from "@tiptap/core";
import { LinkViewContainer } from "@/components/editors/link-view-container";
export const LinkContainer = ({
editor,
containerRef,
}: {
editor: Editor;
containerRef: React.RefObject<HTMLDivElement>;
}) => (
<>
<LinkViewContainer editor={editor} containerRef={containerRef} />
</>
);
@@ -91,7 +91,6 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => {
editorContainerClassName={cn(editorContainerClassNames, "document-editor")}
id={id}
tabIndex={tabIndex}
disabledExtensions={disabledExtensions}
/>
);
};
@@ -3,7 +3,7 @@ import { Editor } from "@tiptap/react";
import { EditorContainer, EditorContentWrapper } from "@/components/editors";
import { AIFeaturesMenu, BlockMenu, EditorBubbleMenu } from "@/components/menus";
// types
import { TAIHandler, TDisplayConfig, TExtensions } from "@/types";
import { TAIHandler, TDisplayConfig } from "@/types";
type IPageRenderer = {
aiHandler?: TAIHandler;
@@ -13,20 +13,10 @@ type IPageRenderer = {
editorContainerClassName: string;
id: string;
tabIndex?: number;
disabledExtensions: TExtensions[];
};
export const PageRenderer = (props: IPageRenderer) => {
const {
aiHandler,
bubbleMenuEnabled,
displayConfig,
editor,
editorContainerClassName,
id,
tabIndex,
disabledExtensions,
} = props;
const { aiHandler, bubbleMenuEnabled, displayConfig, editor, editorContainerClassName, id, tabIndex } = props;
return (
<div className="frame-renderer flex-grow w-full">
@@ -40,7 +30,7 @@ export const PageRenderer = (props: IPageRenderer) => {
{editor.isEditable && (
<div>
{bubbleMenuEnabled && <EditorBubbleMenu editor={editor} />}
<BlockMenu editor={editor} disabledExtensions={disabledExtensions} />
<BlockMenu editor={editor} />
<AIFeaturesMenu menu={aiHandler?.menu} />
</div>
)}
@@ -83,7 +83,6 @@ const DocumentReadOnlyEditor = (props: IDocumentReadOnlyEditor) => {
editor={editor}
editorContainerClassName={cn(editorContainerClassName, "document-editor")}
id={id}
disabledExtensions={disabledExtensions}
/>
);
};
@@ -9,7 +9,6 @@ import { CORE_EXTENSIONS } from "@/constants/extension";
import { TDisplayConfig } from "@/types";
// components
import { LinkViewContainer } from "./link-view-container";
import { LinkContainer } from "@/plane-editor/components/link-container";
interface EditorContainerProps {
children: ReactNode;
@@ -97,7 +96,7 @@ export const EditorContainer: FC<EditorContainerProps> = (props) => {
)}
>
{children}
<LinkContainer editor={editor} containerRef={containerRef} />
<LinkViewContainer editor={editor} containerRef={containerRef} />
</div>
</>
);
@@ -3,7 +3,6 @@ import { Editor, useEditorState } from "@tiptap/react";
import { FC, useCallback, useEffect, useState } from "react";
// components
import { LinkView, LinkViewProps } from "@/components/links";
import { getExtensionStorage } from "@/helpers/get-extension-storage";
interface LinkViewContainerProps {
editor: Editor;
@@ -18,7 +17,7 @@ export const LinkViewContainer: FC<LinkViewContainerProps> = ({ editor, containe
const editorState = useEditorState({
editor,
selector: ({ editor }: { editor: Editor }) => ({
linkExtensionStorage: getExtensionStorage(editor, "link"),
linkExtensionStorage: editor.storage.link,
}),
});
@@ -110,7 +109,7 @@ export const LinkViewContainer: FC<LinkViewContainerProps> = ({ editor, containe
// Close link view when bubble menu opens
useEffect(() => {
if (editorState.linkExtensionStorage?.isBubbleMenuOpen && isOpen) {
if (editorState.linkExtensionStorage.isBubbleMenuOpen && isOpen) {
setIsOpen(false);
}
}, [editorState.linkExtensionStorage, isOpen]);
@@ -4,11 +4,9 @@ import { useCallback, useEffect, useRef } from "react";
import tippy, { Instance } from "tippy.js";
// constants
import { CORE_EXTENSIONS } from "@/constants/extension";
import { TExtensions } from "@/types";
interface BlockMenuProps {
editor: Editor;
disabledExtensions?: TExtensions[];
}
export const BlockMenu = (props: BlockMenuProps) => {
@@ -3,7 +3,7 @@ import React, { useRef, useState, useCallback, useLayoutEffect, useEffect } from
// plane utils
import { cn } from "@plane/utils";
// extensions
import { CustomBaseImageNodeViewProps, ImageToolbarRoot } from "@/extensions/custom-image";
import { CustoBaseImageNodeViewProps, ImageToolbarRoot } from "@/extensions/custom-image";
import { ImageUploadStatus } from "./upload-status";
const MIN_SIZE = 100;
@@ -38,7 +38,7 @@ const ensurePixelString = <TDefault,>(value: Pixel | TDefault | number | undefin
return value;
};
type CustomImageBlockProps = CustomBaseImageNodeViewProps & {
type CustomImageBlockProps = CustoBaseImageNodeViewProps & {
imageFromFileSystem: string | undefined;
setFailedToLoadImage: (isError: boolean) => void;
editorContainer: HTMLDivElement | null;
@@ -7,7 +7,7 @@ import { CustomImageBlock, CustomImageUploader, ImageAttributes } from "@/extens
// helpers
import { getExtensionStorage } from "@/helpers/get-extension-storage";
export type CustomBaseImageNodeViewProps = {
export type CustoBaseImageNodeViewProps = {
getPos: () => number;
editor: Editor;
node: NodeViewProps["node"] & {
@@ -17,7 +17,7 @@ export type CustomBaseImageNodeViewProps = {
selected: boolean;
};
export type CustomImageNodeProps = NodeViewProps & CustomBaseImageNodeViewProps;
export type CustomImageNodeProps = NodeViewProps & CustoBaseImageNodeViewProps;
export const CustomImageNode = (props: CustomImageNodeProps) => {
const { getPos, editor, node, updateAttributes, selected } = props;
@@ -6,14 +6,12 @@ import { cn } from "@plane/utils";
import { ACCEPTED_IMAGE_MIME_TYPES } from "@/constants/config";
import { CORE_EXTENSIONS } from "@/constants/extension";
// extensions
import { CustomBaseImageNodeViewProps, getImageComponentImageFileMap } from "@/extensions/custom-image";
// helpers
import { EFileError } from "@/helpers/file";
import { getExtensionStorage } from "@/helpers/get-extension-storage";
import { CustoBaseImageNodeViewProps, getImageComponentImageFileMap } from "@/extensions/custom-image";
// hooks
import { useUploader, useDropZone, uploadFirstFileAndInsertRemaining } from "@/hooks/use-file-upload";
import { getExtensionStorage } from "@/helpers/get-extension-storage";
type CustomImageUploaderProps = CustomBaseImageNodeViewProps & {
type CustomImageUploaderProps = CustoBaseImageNodeViewProps & {
maxFileSize: number;
loadImageFromFileSystem: (file: string) => void;
failedToLoadImage: boolean;
@@ -73,37 +71,23 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
},
[imageComponentImageFileMap, imageEntityId, updateAttributes, getPos]
);
const uploadImageEditorCommand = useCallback(
async (file: File) => await editor?.commands.uploadImage(imageEntityId ?? "", file),
[editor, imageEntityId]
);
const handleProgressStatus = useCallback(
(isUploading: boolean) => {
getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY).uploadInProgress = isUploading;
},
[editor]
);
const handleInvalidFile = useCallback((_error: EFileError, _file: File, message: string) => {
alert(message);
}, []);
// hooks
const { isUploading: isImageBeingUploaded, uploadFile } = useUploader({
acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES,
// @ts-expect-error - TODO: fix typings, and don't remove await from here for now
editorCommand: uploadImageEditorCommand,
handleProgressStatus,
editorCommand: async (file) => await editor?.commands.uploadImage(imageEntityId, file),
handleProgressStatus: (isUploading) => {
getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY).uploadInProgress = isUploading;
},
loadFileFromFileSystem: loadImageFromFileSystem,
maxFileSize,
onInvalidFile: handleInvalidFile,
onUpload,
});
const { draggedInside, onDrop, onDragEnter, onDragLeave } = useDropZone({
acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES,
editor,
maxFileSize,
onInvalidFile: (_error, message) => alert(message),
pos: getPos(),
type: "image",
uploader: uploadFile,
@@ -138,8 +122,11 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
return;
}
await uploadFirstFileAndInsertRemaining({
acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES,
editor,
filesList,
maxFileSize,
onInvalidFile: (_error, message) => alert(message),
pos: getPos(),
type: "image",
uploader: uploadFile,
@@ -79,7 +79,6 @@ declare module "@tiptap/core" {
export type CustomLinkStorage = {
isPreviewOpen: boolean;
posToInsert: { from: number; to: number };
isBubbleMenuOpen: boolean;
};
export const CustomLinkExtension = Mark.create<LinkOptions, CustomLinkStorage>({
+2 -3
View File
@@ -81,7 +81,6 @@ export const useEditor = (props: CustomEditorProps) => {
immediatelyRender: false,
shouldRerenderOnTransaction: false,
autofocus,
parseOptions: { preserveWhitespace: true },
editorProps: {
...CoreEditorProps({
editorClassName,
@@ -120,7 +119,7 @@ export const useEditor = (props: CustomEditorProps) => {
const isUploadInProgress = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.uploadInProgress;
if (!editor.isDestroyed && !isUploadInProgress) {
try {
editor.commands.setContent(value, false, { preserveWhitespace: true });
editor.commands.setContent(value, false, { preserveWhitespace: "full" });
if (editor.state.selection) {
const docLength = editor.state.doc.content.size;
const relativePosition = Math.min(editor.state.selection.from, docLength - 1);
@@ -154,7 +153,7 @@ export const useEditor = (props: CustomEditorProps) => {
editor?.chain().setMeta(CORE_EDITOR_META.SKIP_FILE_DELETION, true).clearContent(emitUpdate).run();
},
setEditorValue: (content: string, emitUpdate = false) => {
editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: true });
editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: "full" });
},
setEditorValueAtCursorPosition: (content: string) => {
if (editor?.state.selection) {
@@ -9,11 +9,11 @@ import { TEditorCommands } from "@/types";
type TUploaderArgs = {
acceptedMimeTypes: string[];
editorCommand: (file: File) => Promise<string | undefined>;
editorCommand: (file: File) => Promise<string>;
handleProgressStatus?: (isUploading: boolean) => void;
loadFileFromFileSystem?: (file: string) => void;
maxFileSize: number;
onInvalidFile: (error: EFileError, file: File, message: string) => void;
onInvalidFile: (error: EFileError, message: string) => void;
onUpload: (url: string, file: File) => void;
};
@@ -38,7 +38,7 @@ export const useUploader = (args: TUploaderArgs) => {
acceptedMimeTypes,
file,
maxFileSize,
onError: (error, message) => onInvalidFile(error, file, message),
onError: onInvalidFile,
});
if (!isValid) {
handleProgressStatus?.(false);
@@ -60,7 +60,7 @@ export const useUploader = (args: TUploaderArgs) => {
};
reader.readAsDataURL(file);
}
const url = await editorCommand(file);
const url: string = await editorCommand(file);
if (!url) {
throw new Error("Something went wrong while uploading the file.");
@@ -74,29 +74,24 @@ export const useUploader = (args: TUploaderArgs) => {
setIsUploading(false);
}
},
[
acceptedMimeTypes,
editorCommand,
handleProgressStatus,
loadFileFromFileSystem,
maxFileSize,
onInvalidFile,
onUpload,
]
[acceptedMimeTypes, editorCommand, handleProgressStatus, loadFileFromFileSystem, maxFileSize, onUpload]
);
return { isUploading, uploadFile };
};
type TDropzoneArgs = {
acceptedMimeTypes: string[];
editor: Editor;
maxFileSize: number;
onInvalidFile: (error: EFileError, message: string) => void;
pos: number;
type: Extract<TEditorCommands, "attachment" | "image">;
uploader: (file: File) => Promise<void>;
};
export const useDropZone = (args: TDropzoneArgs) => {
const { editor, pos, type, uploader } = args;
const { acceptedMimeTypes, editor, maxFileSize, onInvalidFile, pos, type, uploader } = args;
// states
const [isDragging, setIsDragging] = useState<boolean>(false);
const [draggedInside, setDraggedInside] = useState<boolean>(false);
@@ -123,21 +118,22 @@ export const useDropZone = (args: TDropzoneArgs) => {
async (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
setDraggedInside(false);
const filesList = e.dataTransfer.files;
if (filesList.length === 0 || !editor.isEditable) {
if (e.dataTransfer.files.length === 0 || !editor.isEditable) {
return;
}
const filesList = e.dataTransfer.files;
await uploadFirstFileAndInsertRemaining({
acceptedMimeTypes,
editor,
filesList,
maxFileSize,
onInvalidFile,
pos,
type,
uploader,
});
},
[editor, pos, type, uploader]
[acceptedMimeTypes, editor, maxFileSize, onInvalidFile, pos, type, uploader]
);
const onDragEnter = useCallback(() => setDraggedInside(true), []);
const onDragLeave = useCallback(() => setDraggedInside(false), []);
@@ -152,8 +148,11 @@ export const useDropZone = (args: TDropzoneArgs) => {
};
type TMultipleFileArgs = {
acceptedMimeTypes: string[];
editor: Editor;
filesList: FileList;
maxFileSize: number;
onInvalidFile: (error: EFileError, message: string) => void;
pos: number;
type: Extract<TEditorCommands, "attachment" | "image">;
uploader: (file: File) => Promise<void>;
@@ -161,18 +160,35 @@ type TMultipleFileArgs = {
// Upload the first file and insert the remaining ones for uploading multiple files
export const uploadFirstFileAndInsertRemaining = async (args: TMultipleFileArgs) => {
const { editor, filesList, pos, type, uploader } = args;
const filesArray = Array.from(filesList);
if (filesArray.length === 0) {
const { acceptedMimeTypes, editor, filesList, maxFileSize, onInvalidFile, pos, type, uploader } = args;
const filteredFiles: File[] = [];
for (let i = 0; i < filesList.length; i += 1) {
const file = filesList.item(i);
if (
file &&
isFileValid({
acceptedMimeTypes,
file,
maxFileSize,
onError: onInvalidFile,
})
) {
filteredFiles.push(file);
}
}
if (filteredFiles.length !== filesList.length) {
console.warn("Some files were invalid and have been ignored.");
}
if (filteredFiles.length === 0) {
console.error("No files found to upload.");
return;
}
// Upload the first file
const firstFile = filesArray[0];
const firstFile = filteredFiles[0];
uploader(firstFile);
// Insert the remaining files
const remainingFiles = filesArray.slice(1);
const remainingFiles = filteredFiles.slice(1);
if (remainingFiles.length > 0) {
const docSize = editor.state.doc.content.size;
const posOfNextFileToBeInserted = Math.min(pos + 1, docSize);
@@ -46,7 +46,6 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
immediatelyRender: true,
shouldRerenderOnTransaction: false,
content: typeof initialValue === "string" && initialValue.trim() !== "" ? initialValue : "<p></p>",
parseOptions: { preserveWhitespace: true },
editorProps: {
...CoreReadOnlyEditorProps({
editorClassName,
@@ -72,7 +71,7 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
// for syncing swr data on tab refocus etc
useEffect(() => {
if (initialValue === null || initialValue === undefined) return;
if (editor && !editor.isDestroyed) editor?.commands.setContent(initialValue, false, { preserveWhitespace: true });
if (editor && !editor.isDestroyed) editor?.commands.setContent(initialValue, false, { preserveWhitespace: "full" });
}, [editor, initialValue]);
useImperativeHandle(forwardedRef, () => ({
@@ -80,7 +79,7 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
editor?.chain().setMeta(CORE_EDITOR_META.SKIP_FILE_DELETION, true).clearContent(emitUpdate).run();
},
setEditorValue: (content: string, emitUpdate = false) => {
editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: true });
editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: "full" });
},
getMarkDown: (): string => {
const markdownOutput = editor?.storage.markdown.getMarkdown();
@@ -1,7 +1,5 @@
import { Editor } from "@tiptap/core";
import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
// constants
import { CORE_EDITOR_META } from "@/constants/meta";
// plane editor imports
import { NODE_FILE_MAP } from "@/plane-editor/constants/utility";
// types
@@ -34,7 +32,7 @@ export const TrackFileDeletionPlugin = (editor: Editor, deleteHandler: TFileHand
transactions.forEach((transaction) => {
// if the transaction has meta of skipFileDeletion set to true, then return (like while clearing the editor content programmatically)
if (transaction.getMeta(CORE_EDITOR_META.SKIP_FILE_DELETION)) return;
if (transaction.getMeta("skipFileDeletion")) return;
const removedFiles: TFileNode[] = [];
-1
View File
@@ -1,5 +1,4 @@
export type TReadOnlyFileHandler = {
checkIfAssetExists: (assetId: string) => Promise<boolean>;
getAssetSrc: (path: string) => Promise<string>;
restore: (assetSrc: string) => Promise<void>;
};
+1 -1
View File
@@ -22,7 +22,7 @@
"@plane/eslint-config": "*",
"@types/node": "^22.5.4",
"@types/react": "^18.3.11",
"tsup": "8.4.0",
"tsup": "^8.5.0",
"typescript": "^5.3.3"
}
}
-9
View File
@@ -24,13 +24,4 @@ export const SUPPORTED_LANGUAGES: ILanguageOption[] = [
{ label: "Türkçe", value: "tr-TR" },
];
/**
* Enum for translation file names
* These are the JSON files that contain translations each category
*/
export enum ETranslationFiles {
TRANSLATIONS = "translations",
ACCESSIBILITY = "accessibility",
}
export const LANGUAGE_STORAGE_KEY = "userLanguage";
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Logo pracovního prostoru",
"open_workspace_switcher": "Otevřít přepínač pracovního prostoru",
"open_user_menu": "Otevřít uživatelské menu",
"open_command_palette": "Otevřít paletu příkazů",
"open_extended_sidebar": "Otevřít rozšířený postranní panel",
"close_extended_sidebar": "Zavřít rozšířený postranní panel",
"create_favorites_folder": "Vytvořit složku oblíbených",
"open_folder": "Otevřít složku",
"close_folder": "Zavřít složku",
"open_favorites_menu": "Otevřít menu oblíbených",
"close_favorites_menu": "Zavřít menu oblíbených",
"enter_folder_name": "Zadejte název složky",
"create_new_project": "Vytvořit nový projekt",
"open_projects_menu": "Otevřít menu projektů",
"close_projects_menu": "Zavřít menu projektů",
"toggle_quick_actions_menu": "Přepnout menu rychlých akcí",
"open_project_menu": "Otevřít menu projektu",
"close_project_menu": "Zavřít menu projektu",
"collapse_sidebar": "Sbalit postranní panel",
"expand_sidebar": "Rozbalit postranní panel",
"edition_badge": "Otevřít modal placených plánů"
},
"auth_forms": {
"clear_email": "Vymazat e-mail",
"show_password": "Zobrazit heslo",
"hide_password": "Skrýt heslo",
"close_alert": "Zavřít upozornění",
"close_popover": "Zavřít vyskakovací okno"
}
}
}
@@ -848,7 +848,6 @@
"live": "Živě",
"change_history": "Historie změn",
"coming_soon": "Již brzy",
"member": "Člen",
"members": "Členové",
"you": "Vy",
"upgrade_cta": {
@@ -1081,9 +1080,7 @@
"select": {
"error": "Vyberte alespoň jednu pracovní položku",
"empty": "Nevybrány žádné pracovní položky",
"add_selected": "Přidat vybrané pracovní položky",
"select_all": "Vybrat vše",
"deselect_all": "Zrušit výběr všeho"
"add_selected": "Přidat vybrané pracovní položky"
},
"open_in_full_screen": "Otevřít pracovní položku na celou obrazovku"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Arbeitsbereich-Logo",
"open_workspace_switcher": "Arbeitsbereich-Umschalter öffnen",
"open_user_menu": "Benutzermenü öffnen",
"open_command_palette": "Befehlspalette öffnen",
"open_extended_sidebar": "Erweiterte Seitenleiste öffnen",
"close_extended_sidebar": "Erweiterte Seitenleiste schließen",
"create_favorites_folder": "Favoriten-Ordner erstellen",
"open_folder": "Ordner öffnen",
"close_folder": "Ordner schließen",
"open_favorites_menu": "Favoriten-Menü öffnen",
"close_favorites_menu": "Favoriten-Menü schließen",
"enter_folder_name": "Ordnername eingeben",
"create_new_project": "Neues Projekt erstellen",
"open_projects_menu": "Projekt-Menü öffnen",
"close_projects_menu": "Projekt-Menü schließen",
"toggle_quick_actions_menu": "Schnellaktionen-Menü umschalten",
"open_project_menu": "Projekt-Menü öffnen",
"close_project_menu": "Projekt-Menü schließen",
"collapse_sidebar": "Seitenleiste einklappen",
"expand_sidebar": "Seitenleiste ausklappen",
"edition_badge": "Modal für kostenpflichtige Pläne öffnen"
},
"auth_forms": {
"clear_email": "E-Mail löschen",
"show_password": "Passwort anzeigen",
"hide_password": "Passwort verbergen",
"close_alert": "Warnung schließen",
"close_popover": "Popover schließen"
}
}
}
@@ -848,7 +848,6 @@
"live": "Live",
"change_history": "Änderungsverlauf",
"coming_soon": "Demnächst verfügbar",
"member": "Mitglied",
"members": "Mitglieder",
"you": "Sie",
"upgrade_cta": {
@@ -1081,9 +1080,7 @@
"select": {
"error": "Wählen Sie mindestens ein Arbeitselement aus",
"empty": "Keine Arbeitselemente ausgewählt",
"add_selected": "Ausgewählte Arbeitselemente hinzufügen",
"select_all": "Alle auswählen",
"deselect_all": "Alle abwählen"
"add_selected": "Ausgewählte Arbeitselemente hinzufügen"
},
"open_in_full_screen": "Arbeitselement im Vollbild öffnen"
},
@@ -2462,4 +2459,4 @@
"previously_edited_by": "Zuvor bearbeitet von",
"edited_by": "Bearbeitet von"
}
}
}
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Workspace logo",
"open_workspace_switcher": "Open workspace switcher",
"open_user_menu": "Open user menu",
"open_command_palette": "Open command palette",
"open_extended_sidebar": "Open extended sidebar",
"close_extended_sidebar": "Close extended sidebar",
"create_favorites_folder": "Create favorites folder",
"open_folder": "Open folder",
"close_folder": "Close folder",
"open_favorites_menu": "Open favorites menu",
"close_favorites_menu": "Close favorites menu",
"enter_folder_name": "Enter folder name",
"create_new_project": "Create new project",
"open_projects_menu": "Open projects menu",
"close_projects_menu": "Close projects menu",
"toggle_quick_actions_menu": "Toggle quick actions menu",
"open_project_menu": "Open project menu",
"close_project_menu": "Close project menu",
"collapse_sidebar": "Collapse sidebar",
"expand_sidebar": "Expand sidebar",
"edition_badge": "Open paid plans' modal"
},
"auth_forms": {
"clear_email": "Clear email",
"show_password": "Show password",
"hide_password": "Hide password",
"close_alert": "Close alert",
"close_popover": "Close popover"
}
}
}
+12 -61
View File
@@ -43,8 +43,7 @@
"your_account": "Your account",
"security": "Security",
"activity": "Activity",
"preferences": "Preferences",
"language_and_time": "Language & Time",
"appearance": "Appearance",
"notifications": "Notifications",
"workspaces": "Workspaces",
"create_workspace": "Create workspace",
@@ -57,10 +56,6 @@
"something_went_wrong_please_try_again": "Something went wrong. Please try again.",
"load_more": "Load more",
"select_or_customize_your_interface_color_scheme": "Select or customize your interface color scheme.",
"timezone_setting": "Current timezone setting.",
"language_setting": "Choose the language used in the user interface.",
"settings_moved_to_preferences": "Timezone & Language settings have been moved to preferences.",
"go_to_preferences": "Go to preferences",
"theme": "Theme",
"system_preference": "System preference",
"light": "Light",
@@ -339,8 +334,6 @@
"new_password_must_be_different_from_old_password": "New password must be different from old password",
"edited": "edited",
"bot": "Bot",
"settings_description": "Manage your account, workspace, and project preferences all in one place. Switch between tabs to easily configure.",
"back_to_workspace": "Back to workspace",
"project_view": {
"sort_by": {
"created_at": "Created at",
@@ -690,7 +683,6 @@
"live": "Live",
"change_history": "Change History",
"coming_soon": "Coming soon",
"member": "Member",
"members": "Members",
"you": "You",
"upgrade_cta": {
@@ -924,9 +916,7 @@
"select": {
"error": "Please select at least one work item",
"empty": "No work items selected",
"add_selected": "Add selected work items",
"select_all": "Select all",
"deselect_all": "Deselect all"
"add_selected": "Add selected work items"
},
"open_in_full_screen": "Open work item in full screen"
},
@@ -1311,28 +1301,6 @@
}
}
},
"account_settings": {
"profile":{},
"preferences":{
"heading": "Preferences",
"description": "Customize your app experience the way you work"
},
"notifications":{
"heading": "Email notifications",
"description": "Stay in the loop on Work items you are subscribed to. Enable this to get notified."
},
"security":{
"heading": "Security"
},
"api_tokens":{
"heading": "Personal Access Tokens",
"description": "Generate secure API tokens to integrate your data with external systems and applications."
},
"activity":{
"heading": "Activity",
"description": "Track your recent actions and changes across all projects and work items."
}
},
"workspace_settings": {
"label": "Workspace settings",
"page_label": "{workspace} - General settings",
@@ -1399,22 +1367,16 @@
}
},
"billing_and_plans": {
"heading": "Billing & Plans",
"description":"Choose your plan, manage subscriptions, and easily upgrade as your needs grow.",
"title": "Billing & Plans",
"current_plan": "Current plan",
"free_plan": "You are currently using the free plan",
"view_plans": "View plans"
},
"exports": {
"heading": "Exports",
"description": "Export your project data in various formats and access your export history with download links.",
"title": "Exports",
"exporting": "Exporting",
"previous_exports": "Previous exports",
"export_separate_files": "Export the data into separate files",
"exporting_projects": "Exporting project",
"format": "Format",
"modal": {
"title": "Export to",
"toasts": {
@@ -1430,8 +1392,6 @@
}
},
"webhooks": {
"heading": "Webhooks",
"description": "Automate notifications to external services when project events occur.",
"title": "Webhooks",
"add_webhook": "Add webhook",
"modal": {
@@ -1483,29 +1443,29 @@
}
},
"api_tokens": {
"title": "Personal Access Tokens",
"add_token": "Add personal access token",
"title": "API Tokens",
"add_token": "Add API token",
"create_token": "Create token",
"never_expires": "Never expires",
"generate_token": "Generate token",
"generating": "Generating",
"delete": {
"title": "Delete personal access token",
"title": "Delete API token",
"description": "Any application using this token will no longer have the access to Plane data. This action cannot be undone.",
"success": {
"title": "Success!",
"message": "The token has been successfully deleted"
"message": "The API token has been successfully deleted"
},
"error": {
"title": "Error!",
"message": "The token could not be deleted"
"message": "The API token could not be deleted"
}
}
}
},
"empty_state": {
"api_tokens": {
"title": "No personal access tokens created",
"title": "No API tokens created",
"description": "Plane APIs can be used to integrate your data in Plane with any external system. Create a token to get started."
},
"webhooks": {
@@ -1555,9 +1515,8 @@
"profile": "Profile",
"security": "Security",
"activity": "Activity",
"preferences": "Preferences",
"notifications": "Notifications",
"api-tokens": "Personal Access Tokens"
"appearance": "Appearance",
"notifications": "Notifications"
},
"tabs": {
"summary": "Summary",
@@ -1619,8 +1578,6 @@
}
},
"states": {
"heading": "States",
"description": "Define and customize workflow states to track the progress of your work items.",
"describe_this_state_for_your_members": "Describe this state for your members.",
"empty_state": {
"title": "No states available for the {groupKey} group",
@@ -1628,8 +1585,6 @@
}
},
"labels": {
"heading": "Labels",
"description": "Create custom labels to categorize and organize your work items",
"label_title": "Label title",
"label_title_is_required": "Label title is required",
"label_max_char": "Label name should not exceed 255 characters",
@@ -1638,11 +1593,9 @@
}
},
"estimates": {
"heading": "Estimates",
"description": "Set up estimation systems to track and communicate the effort required for each work item.",
"label": "Estimates",
"title": "Enable estimates for my project",
"enable_description": "They help you in communicating complexity and workload of the team.",
"description": "They help you in communicating complexity and workload of the team.",
"no_estimate": "No estimate",
"new": "New estimate system",
"create": {
@@ -1724,8 +1677,6 @@
},
"automations": {
"label": "Automations",
"heading": "Automations",
"description": "Configure automated actions to streamline your project management workflow and reduce manual tasks.",
"auto-archive": {
"title": "Auto-archive closed work items",
"description": "Plane will auto archive work items that have been completed or canceled.",
@@ -2345,4 +2296,4 @@
"previously_edited_by": "Previously edited by",
"edited_by": "Edited by"
}
}
}
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Logo del espacio de trabajo",
"open_workspace_switcher": "Abrir cambiador de espacio de trabajo",
"open_user_menu": "Abrir menú de usuario",
"open_command_palette": "Abrir paleta de comandos",
"open_extended_sidebar": "Abrir barra lateral extendida",
"close_extended_sidebar": "Cerrar barra lateral extendida",
"create_favorites_folder": "Crear carpeta de favoritos",
"open_folder": "Abrir carpeta",
"close_folder": "Cerrar carpeta",
"open_favorites_menu": "Abrir menú de favoritos",
"close_favorites_menu": "Cerrar menú de favoritos",
"enter_folder_name": "Ingresar nombre de carpeta",
"create_new_project": "Crear nuevo proyecto",
"open_projects_menu": "Abrir menú de proyectos",
"close_projects_menu": "Cerrar menú de proyectos",
"toggle_quick_actions_menu": "Alternar menú de acciones rápidas",
"open_project_menu": "Abrir menú de proyecto",
"close_project_menu": "Cerrar menú de proyecto",
"collapse_sidebar": "Colapsar barra lateral",
"expand_sidebar": "Expandir barra lateral",
"edition_badge": "Abrir modal de planes de pago"
},
"auth_forms": {
"clear_email": "Limpiar correo electrónico",
"show_password": "Mostrar contraseña",
"hide_password": "Ocultar contraseña",
"close_alert": "Cerrar alerta",
"close_popover": "Cerrar ventana emergente"
}
}
}
@@ -851,7 +851,6 @@
"live": "En vivo",
"change_history": "Historial de cambios",
"coming_soon": "Próximamente",
"member": "Miembro",
"members": "Miembros",
"you": "Tú",
"upgrade_cta": {
@@ -1084,9 +1083,7 @@
"select": {
"error": "Por favor selecciona al menos un elemento de trabajo",
"empty": "No hay elementos de trabajo seleccionados",
"add_selected": "Agregar elementos seleccionados",
"select_all": "Seleccionar todo",
"deselect_all": "Deseleccionar todo"
"add_selected": "Agregar elementos seleccionados"
},
"open_in_full_screen": "Abrir elemento de trabajo en pantalla completa"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Logo de l'espace de travail",
"open_workspace_switcher": "Ouvrir le sélecteur d'espace de travail",
"open_user_menu": "Ouvrir le menu utilisateur",
"open_command_palette": "Ouvrir la palette de commandes",
"open_extended_sidebar": "Ouvrir la barre latérale étendue",
"close_extended_sidebar": "Fermer la barre latérale étendue",
"create_favorites_folder": "Créer un dossier de favoris",
"open_folder": "Ouvrir le dossier",
"close_folder": "Fermer le dossier",
"open_favorites_menu": "Ouvrir le menu des favoris",
"close_favorites_menu": "Fermer le menu des favoris",
"enter_folder_name": "Saisir le nom du dossier",
"create_new_project": "Créer un nouveau projet",
"open_projects_menu": "Ouvrir le menu des projets",
"close_projects_menu": "Fermer le menu des projets",
"toggle_quick_actions_menu": "Basculer le menu d'actions rapides",
"open_project_menu": "Ouvrir le menu du projet",
"close_project_menu": "Fermer le menu du projet",
"collapse_sidebar": "Réduire la barre latérale",
"expand_sidebar": "Étendre la barre latérale",
"edition_badge": "Ouvrir le modal des plans payants"
},
"auth_forms": {
"clear_email": "Effacer l'e-mail",
"show_password": "Afficher le mot de passe",
"hide_password": "Masquer le mot de passe",
"close_alert": "Fermer l'alerte",
"close_popover": "Fermer la fenêtre contextuelle"
}
}
}
@@ -849,7 +849,6 @@
"live": "En direct",
"change_history": "Historique des modifications",
"coming_soon": "À venir",
"member": "Membre",
"members": "Membres",
"you": "Vous",
"upgrade_cta": {
@@ -1082,9 +1081,7 @@
"select": {
"error": "Veuillez sélectionner au moins un élément de travail",
"empty": "Aucun élément de travail sélectionné",
"add_selected": "Ajouter les éléments de travail sélectionnés",
"select_all": "Sélectionner tout",
"deselect_all": "Tout désélectionner"
"add_selected": "Ajouter les éléments de travail sélectionnés"
},
"open_in_full_screen": "Ouvrir l'élément de travail en plein écran"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Logo ruang kerja",
"open_workspace_switcher": "Buka penukar ruang kerja",
"open_user_menu": "Buka menu pengguna",
"open_command_palette": "Buka palet perintah",
"open_extended_sidebar": "Buka sidebar diperluas",
"close_extended_sidebar": "Tutup sidebar diperluas",
"create_favorites_folder": "Buat folder favorit",
"open_folder": "Buka folder",
"close_folder": "Tutup folder",
"open_favorites_menu": "Buka menu favorit",
"close_favorites_menu": "Tutup menu favorit",
"enter_folder_name": "Masukkan nama folder",
"create_new_project": "Buat proyek baru",
"open_projects_menu": "Buka menu proyek",
"close_projects_menu": "Tutup menu proyek",
"toggle_quick_actions_menu": "Alihkan menu tindakan cepat",
"open_project_menu": "Buka menu proyek",
"close_project_menu": "Tutup menu proyek",
"collapse_sidebar": "Tutup sidebar",
"expand_sidebar": "Perluas sidebar",
"edition_badge": "Buka modal paket berbayar"
},
"auth_forms": {
"clear_email": "Hapus email",
"show_password": "Tampilkan kata sandi",
"hide_password": "Sembunyikan kata sandi",
"close_alert": "Tutup peringatan",
"close_popover": "Tutup popover"
}
}
}
@@ -848,7 +848,6 @@
"live": "Langsung",
"change_history": "Riwayat Perubahan",
"coming_soon": "Segera hadir",
"member": "Anggota",
"members": "Anggota",
"you": "Anda",
"upgrade_cta": {
@@ -1081,9 +1080,7 @@
"select": {
"error": "Silakan pilih setidaknya satu item kerja",
"empty": "Tidak ada item kerja yang dipilih",
"add_selected": "Tambah item kerja yang dipilih",
"select_all": "Pilih semua item kerja",
"deselect_all": "Batalkan pilihan semua item kerja"
"add_selected": "Tambah item kerja yang dipilih"
},
"open_in_full_screen": "Buka item kerja dalam layar penuh"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Logo dell'area di lavoro",
"open_workspace_switcher": "Apri selettore area di lavoro",
"open_user_menu": "Apri menu utente",
"open_command_palette": "Apri tavolozza comandi",
"open_extended_sidebar": "Apri barra laterale estesa",
"close_extended_sidebar": "Chiudi barra laterale estesa",
"create_favorites_folder": "Crea cartella preferiti",
"open_folder": "Apri cartella",
"close_folder": "Chiudi cartella",
"open_favorites_menu": "Apri menu preferiti",
"close_favorites_menu": "Chiudi menu preferiti",
"enter_folder_name": "Inserisci nome cartella",
"create_new_project": "Crea nuovo progetto",
"open_projects_menu": "Apri menu progetti",
"close_projects_menu": "Chiudi menu progetti",
"toggle_quick_actions_menu": "Attiva/disattiva menu azioni rapide",
"open_project_menu": "Apri menu progetto",
"close_project_menu": "Chiudi menu progetto",
"collapse_sidebar": "Comprimi barra laterale",
"expand_sidebar": "Espandi barra laterale",
"edition_badge": "Apri modal piani a pagamento"
},
"auth_forms": {
"clear_email": "Cancella email",
"show_password": "Mostra password",
"hide_password": "Nascondi password",
"close_alert": "Chiudi avviso",
"close_popover": "Chiudi popover"
}
}
}
@@ -847,7 +847,6 @@
"live": "Live",
"change_history": "Cronologia modifiche",
"coming_soon": "Prossimamente",
"member": "Membro",
"members": "Membri",
"you": "Tu",
"upgrade_cta": {
@@ -1080,9 +1079,7 @@
"select": {
"error": "Seleziona almeno un elemento di lavoro",
"empty": "Nessun elemento di lavoro selezionato",
"add_selected": "Aggiungi gli elementi di lavoro selezionati",
"select_all": "Seleziona tutto",
"deselect_all": "Deseleziona tutto"
"add_selected": "Aggiungi gli elementi di lavoro selezionati"
},
"open_in_full_screen": "Apri l'elemento di lavoro a schermo intero"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "ワークスペースロゴ",
"open_workspace_switcher": "ワークスペーススイッチャーを開く",
"open_user_menu": "ユーザーメニューを開く",
"open_command_palette": "コマンドパレットを開く",
"open_extended_sidebar": "拡張サイドバーを開く",
"close_extended_sidebar": "拡張サイドバーを閉じる",
"create_favorites_folder": "お気に入りフォルダを作成",
"open_folder": "フォルダを開く",
"close_folder": "フォルダを閉じる",
"open_favorites_menu": "お気に入りメニューを開く",
"close_favorites_menu": "お気に入りメニューを閉じる",
"enter_folder_name": "フォルダ名を入力",
"create_new_project": "新しいプロジェクトを作成",
"open_projects_menu": "プロジェクトメニューを開く",
"close_projects_menu": "プロジェクトメニューを閉じる",
"toggle_quick_actions_menu": "クイックアクションメニューの切り替え",
"open_project_menu": "プロジェクトメニューを開く",
"close_project_menu": "プロジェクトメニューを閉じる",
"collapse_sidebar": "サイドバーを折りたたむ",
"expand_sidebar": "サイドバーを展開",
"edition_badge": "有料プランのモーダルを開く"
},
"auth_forms": {
"clear_email": "メールをクリア",
"show_password": "パスワードを表示",
"hide_password": "パスワードを非表示",
"close_alert": "アラートを閉じる",
"close_popover": "ポップオーバーを閉じる"
}
}
}
@@ -849,7 +849,6 @@
"live": "ライブ",
"change_history": "変更履歴",
"coming_soon": "近日公開",
"member": "メンバー",
"members": "メンバー",
"you": "あなた",
"upgrade_cta": {
@@ -1082,9 +1081,7 @@
"select": {
"error": "少なくとも1つの作業項目を選択してください",
"empty": "作業項目が選択されていません",
"add_selected": "選択した作業項目を追加",
"select_all": "すべて選択",
"deselect_all": "すべての選択を解除"
"add_selected": "選択した作業項目を追加"
},
"open_in_full_screen": "作業項目をフルスクリーンで開く"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "워크스페이스 로고",
"open_workspace_switcher": "워크스페이스 전환기 열기",
"open_user_menu": "사용자 메뉴 열기",
"open_command_palette": "명령 팔레트 열기",
"open_extended_sidebar": "확장된 사이드바 열기",
"close_extended_sidebar": "확장된 사이드바 닫기",
"create_favorites_folder": "즐겨찾기 폴더 생성",
"open_folder": "폴더 열기",
"close_folder": "폴더 닫기",
"open_favorites_menu": "즐겨찾기 메뉴 열기",
"close_favorites_menu": "즐겨찾기 메뉴 닫기",
"enter_folder_name": "폴더 이름 입력",
"create_new_project": "새 프로젝트 생성",
"open_projects_menu": "프로젝트 메뉴 열기",
"close_projects_menu": "프로젝트 메뉴 닫기",
"toggle_quick_actions_menu": "빠른 작업 메뉴 토글",
"open_project_menu": "프로젝트 메뉴 열기",
"close_project_menu": "프로젝트 메뉴 닫기",
"collapse_sidebar": "사이드바 축소",
"expand_sidebar": "사이드바 확장",
"edition_badge": "유료 플랜 모달 열기"
},
"auth_forms": {
"clear_email": "이메일 지우기",
"show_password": "비밀번호 표시",
"hide_password": "비밀번호 숨기기",
"close_alert": "알림 닫기",
"close_popover": "팝오버 닫기"
}
}
}
@@ -850,7 +850,6 @@
"live": "라이브",
"change_history": "변경 기록",
"coming_soon": "곧 출시",
"member": "멤버",
"members": "멤버",
"you": "나",
"upgrade_cta": {
@@ -1083,9 +1082,7 @@
"select": {
"error": "최소 하나의 작업 항목을 선택하세요",
"empty": "선택된 작업 항목 없음",
"add_selected": "선택된 작업 항목 추가",
"select_all": "모두 선택",
"deselect_all": "모두 선택 해제"
"add_selected": "선택된 작업 항목 추가"
},
"open_in_full_screen": "작업 항목을 전체 화면으로 열기"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Logo obszaru roboczego",
"open_workspace_switcher": "Otwórz przełącznik obszaru roboczego",
"open_user_menu": "Otwórz menu użytkownika",
"open_command_palette": "Otwórz paletę poleceń",
"open_extended_sidebar": "Otwórz rozszerzoną pasek boczny",
"close_extended_sidebar": "Zamknij rozszerzoną pasek boczny",
"create_favorites_folder": "Utwórz folder ulubionych",
"open_folder": "Otwórz folder",
"close_folder": "Zamknij folder",
"open_favorites_menu": "Otwórz menu ulubionych",
"close_favorites_menu": "Zamknij menu ulubionych",
"enter_folder_name": "Wprowadź nazwę folderu",
"create_new_project": "Utwórz nowy projekt",
"open_projects_menu": "Otwórz menu projektów",
"close_projects_menu": "Zamknij menu projektów",
"toggle_quick_actions_menu": "Przełącz menu szybkich akcji",
"open_project_menu": "Otwórz menu projektu",
"close_project_menu": "Zamknij menu projektu",
"collapse_sidebar": "Zwiń pasek boczny",
"expand_sidebar": "Rozwiń pasek boczny",
"edition_badge": "Otwórz modal płatnych planów"
},
"auth_forms": {
"clear_email": "Wyczyść e-mail",
"show_password": "Pokaż hasło",
"hide_password": "Ukryj hasło",
"close_alert": "Zamknij alert",
"close_popover": "Zamknij popover"
}
}
}
@@ -850,7 +850,6 @@
"live": "Na żywo",
"change_history": "Historia zmian",
"coming_soon": "Wkrótce",
"member": "Członek",
"members": "Członkowie",
"you": "Ty",
"upgrade_cta": {
@@ -1083,9 +1082,7 @@
"select": {
"error": "Wybierz co najmniej jeden element pracy",
"empty": "Nie wybrano żadnych elementów pracy",
"add_selected": "Dodaj wybrane elementy pracy",
"select_all": "Wybierz wszystko",
"deselect_all": "Odznacz wszystko"
"add_selected": "Dodaj wybrane elementy pracy"
},
"open_in_full_screen": "Otwórz element pracy na pełnym ekranie"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Logo do espaço de trabalho",
"open_workspace_switcher": "Abrir seletor de espaço de trabalho",
"open_user_menu": "Abrir menu do usuário",
"open_command_palette": "Abrir paleta de comandos",
"open_extended_sidebar": "Abrir barra lateral estendida",
"close_extended_sidebar": "Fechar barra lateral estendida",
"create_favorites_folder": "Criar pasta de favoritos",
"open_folder": "Abrir pasta",
"close_folder": "Fechar pasta",
"open_favorites_menu": "Abrir menu de favoritos",
"close_favorites_menu": "Fechar menu de favoritos",
"enter_folder_name": "Digite o nome da pasta",
"create_new_project": "Criar novo projeto",
"open_projects_menu": "Abrir menu de projetos",
"close_projects_menu": "Fechar menu de projetos",
"toggle_quick_actions_menu": "Alternar menu de ações rápidas",
"open_project_menu": "Abrir menu do projeto",
"close_project_menu": "Fechar menu do projeto",
"collapse_sidebar": "Recolher barra lateral",
"expand_sidebar": "Expandir barra lateral",
"edition_badge": "Abrir modal de planos pagos"
},
"auth_forms": {
"clear_email": "Limpar e-mail",
"show_password": "Mostrar senha",
"hide_password": "Ocultar senha",
"close_alert": "Fechar alerta",
"close_popover": "Fechar popover"
}
}
}
@@ -850,7 +850,6 @@
"live": "Ao vivo",
"change_history": "Histórico de alterações",
"coming_soon": "Em breve",
"member": "Membro",
"members": "Membros",
"you": "Você",
"upgrade_cta": {
@@ -1083,9 +1082,7 @@
"select": {
"error": "Selecione pelo menos um item de trabalho",
"empty": "Nenhum item de trabalho selecionado",
"add_selected": "Adicionar itens de trabalho selecionados",
"select_all": "Selecionar tudo",
"deselect_all": "Desmarcar tudo"
"add_selected": "Adicionar itens de trabalho selecionados"
},
"open_in_full_screen": "Abrir item de trabalho em tela cheia"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Logo spațiu de lucru",
"open_workspace_switcher": "Deschide comutator spațiu de lucru",
"open_user_menu": "Deschide meniul utilizatorului",
"open_command_palette": "Deschide paleta de comenzi",
"open_extended_sidebar": "Deschide bara laterală extinsă",
"close_extended_sidebar": "Închide bara laterală extinsă",
"create_favorites_folder": "Creează folder de favorite",
"open_folder": "Deschide folderul",
"close_folder": "Închide folderul",
"open_favorites_menu": "Deschide meniul de favorite",
"close_favorites_menu": "Închide meniul de favorite",
"enter_folder_name": "Introduceți numele folderului",
"create_new_project": "Creează proiect nou",
"open_projects_menu": "Deschide meniul de proiecte",
"close_projects_menu": "Închide meniul de proiecte",
"toggle_quick_actions_menu": "Comută meniul de acțiuni rapide",
"open_project_menu": "Deschide meniul proiectului",
"close_project_menu": "Închide meniul proiectului",
"collapse_sidebar": "Restrânge bara laterală",
"expand_sidebar": "Extinde bara laterală",
"edition_badge": "Deschide modalul planurilor plătite"
},
"auth_forms": {
"clear_email": "Șterge e-mailul",
"show_password": "Afișează parola",
"hide_password": "Ascunde parola",
"close_alert": "Închide alerta",
"close_popover": "Închide popover-ul"
}
}
}
@@ -848,7 +848,6 @@
"live": "În direct",
"change_history": "Istoric modificări",
"coming_soon": "În curând",
"member": "Membru",
"members": "Membri",
"you": "Tu",
"upgrade_cta": {
@@ -1081,9 +1080,7 @@
"select": {
"error": "Selectează cel puțin o activitate",
"empty": "Nicio activitate selectată",
"add_selected": "Adaugă activitățile selectate",
"select_all": "Selectează tot",
"deselect_all": "Deselează tot"
"add_selected": "Adaugă activitățile selectate"
},
"open_in_full_screen": "Deschide activitatea pe tot ecranul"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Логотип рабочей области",
"open_workspace_switcher": "Открыть переключатель рабочей области",
"open_user_menu": "Открыть пользовательское меню",
"open_command_palette": "Открыть палитру команд",
"open_extended_sidebar": "Открыть расширенную боковую панель",
"close_extended_sidebar": "Закрыть расширенную боковую панель",
"create_favorites_folder": "Создать папку избранного",
"open_folder": "Открыть папку",
"close_folder": "Закрыть папку",
"open_favorites_menu": "Открыть меню избранного",
"close_favorites_menu": "Закрыть меню избранного",
"enter_folder_name": "Введите имя папки",
"create_new_project": "Создать новый проект",
"open_projects_menu": "Открыть меню проектов",
"close_projects_menu": "Закрыть меню проектов",
"toggle_quick_actions_menu": "Переключить меню быстрых действий",
"open_project_menu": "Открыть меню проекта",
"close_project_menu": "Закрыть меню проекта",
"collapse_sidebar": "Свернуть боковую панель",
"expand_sidebar": "Развернуть боковую панель",
"edition_badge": "Открыть модал платных планов"
},
"auth_forms": {
"clear_email": "Очистить email",
"show_password": "Показать пароль",
"hide_password": "Скрыть пароль",
"close_alert": "Закрыть уведомление",
"close_popover": "Закрыть всплывающее окно"
}
}
}
@@ -850,7 +850,6 @@
"live": "В прямом эфире",
"change_history": "История изменений",
"coming_soon": "Скоро",
"member": "Участник",
"members": "Участники",
"you": "Вы",
"upgrade_cta": {
@@ -1083,9 +1082,7 @@
"select": {
"error": "Выберите хотя бы один рабочий элемент",
"empty": "Рабочие элементы не выбраны",
"add_selected": "Добавить выбранные рабочие элементы",
"select_all": "Выбрать все",
"deselect_all": "Снять выделение со всех"
"add_selected": "Добавить выбранные рабочие элементы"
},
"open_in_full_screen": "Открыть рабочий элемент в полном экране"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Logo pracovného priestoru",
"open_workspace_switcher": "Otvoriť prepínač pracovného priestoru",
"open_user_menu": "Otvoriť používateľské menu",
"open_command_palette": "Otvoriť paletu príkazov",
"open_extended_sidebar": "Otvoriť rozšírený bočný panel",
"close_extended_sidebar": "Zavrieť rozšírený bočný panel",
"create_favorites_folder": "Vytvoriť priečinok obľúbených",
"open_folder": "Otvoriť priečinok",
"close_folder": "Zavrieť priečinok",
"open_favorites_menu": "Otvoriť menu obľúbených",
"close_favorites_menu": "Zavrieť menu obľúbených",
"enter_folder_name": "Zadajte názov priečinka",
"create_new_project": "Vytvoriť nový projekt",
"open_projects_menu": "Otvoriť menu projektov",
"close_projects_menu": "Zavrieť menu projektov",
"toggle_quick_actions_menu": "Prepnúť menu rýchlych akcií",
"open_project_menu": "Otvoriť menu projektu",
"close_project_menu": "Zavrieť menu projektu",
"collapse_sidebar": "Zbaliť bočný panel",
"expand_sidebar": "Rozbaliť bočný panel",
"edition_badge": "Otvoriť modal platených plánov"
},
"auth_forms": {
"clear_email": "Vymazať e-mail",
"show_password": "Zobraziť heslo",
"hide_password": "Skryť heslo",
"close_alert": "Zavrieť upozornenie",
"close_popover": "Zavrieť vyskakovacie okno"
}
}
}
@@ -850,7 +850,6 @@
"live": "Živé",
"change_history": "História zmien",
"coming_soon": "Už čoskoro",
"member": "Člen",
"members": "Členovia",
"you": "Vy",
"upgrade_cta": {
@@ -1083,9 +1082,7 @@
"select": {
"error": "Vyberte aspoň jednu pracovnú položku",
"empty": "Nie sú vybrané žiadne pracovné položky",
"add_selected": "Pridať vybrané pracovné položky",
"select_all": "Vybrať všetko",
"deselect_all": "Zrušiť výber všetkého"
"add_selected": "Pridať vybrané pracovné položky"
},
"open_in_full_screen": "Otvoriť pracovnú položku na celú obrazovku"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Çalışma alanı logosu",
"open_workspace_switcher": "Çalışma alanı değiştiricisini aç",
"open_user_menu": "Kullanıcı menüsünü aç",
"open_command_palette": "Komut paletini aç",
"open_extended_sidebar": "Genişletilmiş kenar çubuğunu aç",
"close_extended_sidebar": "Genişletilmiş kenar çubuğunu kapat",
"create_favorites_folder": "Favoriler klasörü oluştur",
"open_folder": "Klasörü aç",
"close_folder": "Klasörü kapat",
"open_favorites_menu": "Favoriler menüsünü aç",
"close_favorites_menu": "Favoriler menüsünü kapat",
"enter_folder_name": "Klasör adını girin",
"create_new_project": "Yeni proje oluştur",
"open_projects_menu": "Projeler menüsünü aç",
"close_projects_menu": "Projeler menüsünü kapat",
"toggle_quick_actions_menu": "Hızlı eylemler menüsünü aç/kapat",
"open_project_menu": "Proje menüsünü aç",
"close_project_menu": "Proje menüsünü kapat",
"collapse_sidebar": "Kenar çubuğunu daralt",
"expand_sidebar": "Kenar çubuğunu genişlet",
"edition_badge": "Ücretli planlar modalını aç"
},
"auth_forms": {
"clear_email": "E-postayı temizle",
"show_password": "Şifreyi göster",
"hide_password": "Şifreyi gizle",
"close_alert": "Uyarıyı kapat",
"close_popover": "Açılır pencereyi kapat"
}
}
}
@@ -851,7 +851,6 @@
"live": "Canlı",
"change_history": "Değişiklik Geçmişi",
"coming_soon": "Çok Yakında",
"member": "Üye",
"members": "Üyeler",
"you": "Siz",
"upgrade_cta": {
@@ -1084,9 +1083,7 @@
"select": {
"error": "Lütfen en az bir iş öğesi seçin",
"empty": "Hiç iş öğesi seçilmedi",
"add_selected": "Seçilen iş öğelerini ekle",
"select_all": "Tümünü seç",
"deselect_all": "Tümünü seçme"
"add_selected": "Seçilen iş öğelerini ekle"
},
"open_in_full_screen": "İş öğesini tam ekranda aç"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Логотип робочого простору",
"open_workspace_switcher": "Відкрити перемикач робочого простору",
"open_user_menu": "Відкрити меню користувача",
"open_command_palette": "Відкрити палітру команд",
"open_extended_sidebar": "Відкрити розширену бічну панель",
"close_extended_sidebar": "Закрити розширену бічну панель",
"create_favorites_folder": "Створити папку улюблених",
"open_folder": "Відкрити папку",
"close_folder": "Закрити папку",
"open_favorites_menu": "Відкрити меню улюблених",
"close_favorites_menu": "Закрити меню улюблених",
"enter_folder_name": "Введіть назву папки",
"create_new_project": "Створити новий проект",
"open_projects_menu": "Відкрити меню проектів",
"close_projects_menu": "Закрити меню проектів",
"toggle_quick_actions_menu": "Перемкнути меню швидких дій",
"open_project_menu": "Відкрити меню проекту",
"close_project_menu": "Закрити меню проекту",
"collapse_sidebar": "Згорнути бічну панель",
"expand_sidebar": "Розгорнути бічну панель",
"edition_badge": "Відкрити модал платних планів"
},
"auth_forms": {
"clear_email": "Очистити email",
"show_password": "Показати пароль",
"hide_password": "Приховати пароль",
"close_alert": "Закрити сповіщення",
"close_popover": "Закрити спливаюче вікно"
}
}
}
@@ -850,7 +850,6 @@
"live": "Наживо",
"change_history": "Історія змін",
"coming_soon": "Незабаром",
"member": "Учасник",
"members": "Учасники",
"you": "Ви",
"upgrade_cta": {
@@ -1083,9 +1082,7 @@
"select": {
"error": "Виберіть принаймні одну робочу одиницю",
"empty": "Не вибрано жодної робочої одиниці",
"add_selected": "Додати вибрані робочі одиниці",
"select_all": "Вибрати всі",
"deselect_all": "Скасувати вибір усіх"
"add_selected": "Додати вибрані робочі одиниці"
},
"open_in_full_screen": "Відкрити робочу одиницю на повний екран"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "Logo không gian làm việc",
"open_workspace_switcher": "Mở trình chuyển đổi không gian làm việc",
"open_user_menu": "Mở menu người dùng",
"open_command_palette": "Mở bảng lệnh",
"open_extended_sidebar": "Mở thanh bên mở rộng",
"close_extended_sidebar": "Đóng thanh bên mở rộng",
"create_favorites_folder": "Tạo thư mục yêu thích",
"open_folder": "Mở thư mục",
"close_folder": "Đóng thư mục",
"open_favorites_menu": "Mở menu yêu thích",
"close_favorites_menu": "Đóng menu yêu thích",
"enter_folder_name": "Nhập tên thư mục",
"create_new_project": "Tạo dự án mới",
"open_projects_menu": "Mở menu dự án",
"close_projects_menu": "Đóng menu dự án",
"toggle_quick_actions_menu": "Bật/tắt menu hành động nhanh",
"open_project_menu": "Mở menu dự án",
"close_project_menu": "Đóng menu dự án",
"collapse_sidebar": "Thu gọn thanh bên",
"expand_sidebar": "Mở rộng thanh bên",
"edition_badge": "Mở modal gói trả phí"
},
"auth_forms": {
"clear_email": "Xóa email",
"show_password": "Hiển thị mật khẩu",
"hide_password": "Ẩn mật khẩu",
"close_alert": "Đóng cảnh báo",
"close_popover": "Đóng popover"
}
}
}
@@ -849,7 +849,6 @@
"live": "Trực tiếp",
"change_history": "Lịch sử thay đổi",
"coming_soon": "Sắp ra mắt",
"member": "Thành viên",
"members": "Thành viên",
"you": "Bạn",
"upgrade_cta": {
@@ -1082,9 +1081,7 @@
"select": {
"error": "Vui lòng chọn ít nhất một mục công việc",
"empty": "Chưa chọn mục công việc",
"add_selected": "Thêm mục công việc đã chọn",
"select_all": "Chọn tất cả",
"deselect_all": "Bỏ chọn tất cả"
"add_selected": "Thêm mục công việc đã chọn"
},
"open_in_full_screen": "Mở mục công việc trong chế độ toàn màn hình"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "工作空间徽标",
"open_workspace_switcher": "打开工作空间切换器",
"open_user_menu": "打开用户菜单",
"open_command_palette": "打开命令面板",
"open_extended_sidebar": "打开扩展侧边栏",
"close_extended_sidebar": "关闭扩展侧边栏",
"create_favorites_folder": "创建收藏夹文件夹",
"open_folder": "打开文件夹",
"close_folder": "关闭文件夹",
"open_favorites_menu": "打开收藏夹菜单",
"close_favorites_menu": "关闭收藏夹菜单",
"enter_folder_name": "输入文件夹名称",
"create_new_project": "创建新项目",
"open_projects_menu": "打开项目菜单",
"close_projects_menu": "关闭项目菜单",
"toggle_quick_actions_menu": "切换快速操作菜单",
"open_project_menu": "打开项目菜单",
"close_project_menu": "关闭项目菜单",
"collapse_sidebar": "折叠侧边栏",
"expand_sidebar": "展开侧边栏",
"edition_badge": "打开付费计划模态框"
},
"auth_forms": {
"clear_email": "清除邮箱",
"show_password": "显示密码",
"hide_password": "隐藏密码",
"close_alert": "关闭警告",
"close_popover": "关闭弹出框"
}
}
}
@@ -849,7 +849,6 @@
"live": "实时",
"change_history": "变更历史",
"coming_soon": "即将推出",
"member": "成员",
"members": "成员",
"you": "你",
"upgrade_cta": {
@@ -1082,9 +1081,7 @@
"select": {
"error": "请至少选择一个工作项",
"empty": "未选择工作项",
"add_selected": "添加所选工作项",
"select_all": "全选",
"deselect_all": "取消全选"
"add_selected": "添加所选工作项"
},
"open_in_full_screen": "在全屏中打开工作项"
},
@@ -1,34 +0,0 @@
{
"aria_labels": {
"projects_sidebar": {
"workspace_logo": "工作空間標誌",
"open_workspace_switcher": "打開工作空間切換器",
"open_user_menu": "打開用戶選單",
"open_command_palette": "打開命令面板",
"open_extended_sidebar": "打開擴展側邊欄",
"close_extended_sidebar": "關閉擴展側邊欄",
"create_favorites_folder": "創建收藏夾文件夾",
"open_folder": "打開文件夾",
"close_folder": "關閉文件夾",
"open_favorites_menu": "打開收藏夾選單",
"close_favorites_menu": "關閉收藏夾選單",
"enter_folder_name": "輸入文件夾名稱",
"create_new_project": "創建新項目",
"open_projects_menu": "打開項目選單",
"close_projects_menu": "關閉項目選單",
"toggle_quick_actions_menu": "切換快速操作選單",
"open_project_menu": "打開項目選單",
"close_project_menu": "關閉項目選單",
"collapse_sidebar": "摺疊側邊欄",
"expand_sidebar": "展開側邊欄",
"edition_badge": "打開付費計劃模態框"
},
"auth_forms": {
"clear_email": "清除電子郵件",
"show_password": "顯示密碼",
"hide_password": "隱藏密碼",
"close_alert": "關閉警告",
"close_popover": "關閉彈出框"
}
}
}
@@ -850,7 +850,6 @@
"live": "即時",
"change_history": "變更歷史記錄",
"coming_soon": "即將推出",
"member": "成員",
"members": "成員",
"you": "您",
"upgrade_cta": {
@@ -1083,9 +1082,7 @@
"select": {
"error": "請至少選擇一個工作事項",
"empty": "未選擇工作事項",
"add_selected": "新增已選取的工作事項",
"select_all": "全選",
"deselect_all": "取消全選"
"add_selected": "新增已選取的工作事項"
},
"open_in_full_screen": "以全螢幕開啟工作事項"
},
+44 -22
View File
@@ -3,7 +3,7 @@ import get from "lodash/get";
import merge from "lodash/merge";
import { makeAutoObservable, runInAction } from "mobx";
// constants
import { FALLBACK_LANGUAGE, SUPPORTED_LANGUAGES, LANGUAGE_STORAGE_KEY, ETranslationFiles } from "../constants";
import { FALLBACK_LANGUAGE, SUPPORTED_LANGUAGES, LANGUAGE_STORAGE_KEY } from "../constants";
// core translations imports
import coreEn from "../locales/en/core.json";
// types
@@ -130,32 +130,54 @@ export class TranslationStore {
}
}
/**
* Helper function to import and merge multiple translation files for a language
* @param language - The language code
* @param files - Array of file names to import (without .json extension)
* @returns Promise that resolves to merged translations
*/
private async importAndMergeFiles(language: TLanguage, files: string[]): Promise<any> {
try {
const importPromises = files.map((file) => import(`../locales/${language}/${file}.json`));
const modules = await Promise.all(importPromises);
const merged = modules.reduce((acc, module) => merge(acc, module.default), {});
return { default: merged };
} catch (error) {
throw new Error(`Failed to import and merge files for ${language}: ${error}`);
}
}
/**
* Imports the translations for the given language
* @param language - The language to import the translations for
* @returns {Promise<any>}
*/
private async importLanguageFile(language: TLanguage): Promise<any> {
const files = Object.values(ETranslationFiles);
return this.importAndMergeFiles(language, files);
private importLanguageFile(language: TLanguage): Promise<any> {
switch (language) {
case "en":
return import("../locales/en/translations.json");
case "fr":
return import("../locales/fr/translations.json");
case "es":
return import("../locales/es/translations.json");
case "ja":
return import("../locales/ja/translations.json");
case "zh-CN":
return import("../locales/zh-CN/translations.json");
case "zh-TW":
return import("../locales/zh-TW/translations.json");
case "ru":
return import("../locales/ru/translations.json");
case "it":
return import("../locales/it/translations.json");
case "cs":
return import("../locales/cs/translations.json");
case "sk":
return import("../locales/sk/translations.json");
case "de":
return import("../locales/de/translations.json");
case "ua":
return import("../locales/ua/translations.json");
case "pl":
return import("../locales/pl/translations.json");
case "ko":
return import("../locales/ko/translations.json");
case "pt-BR":
return import("../locales/pt-BR/translations.json");
case "id":
return import("../locales/id/translations.json");
case "ro":
return import("../locales/ro/translations.json");
case "vi-VN":
return import("../locales/vi-VN/translations.json");
case "tr-TR":
return import("../locales/tr-TR/translations.json");
default:
throw new Error(`Unsupported language: ${language}`);
}
}
/** Checks if the language is valid based on the supported languages */
-1
View File
@@ -6,7 +6,6 @@
"main": "tailwind.config.js",
"private": true,
"devDependencies": {
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.9",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.38",
@@ -469,7 +469,6 @@ module.exports = {
plugins: [
require("tailwindcss-animate"),
require("@tailwindcss/typography"),
require("@tailwindcss/container-queries"),
function ({ addUtilities }) {
const newUtilities = {
// Mobile screens
+33 -14
View File
@@ -1,5 +1,14 @@
import { EUserProjectRoles } from "@plane/constants";
import type { IUser, IUserLite, IWorkspace, TLogoProps, TStateGroups } from "..";
import type {
IProjectViewProps,
IUser,
IUserLite,
IUserMemberLite,
IWorkspace,
IWorkspaceLite,
TLogoProps,
TStateGroups,
} from "..";
import { TUserPermissions } from "../enums";
export interface IPartialProject {
@@ -82,20 +91,30 @@ export interface IProjectMemberLite {
member_id: string;
}
export type TProjectMembership = {
export interface IProjectMember {
id: string;
member: IUserMemberLite;
project: IProjectLite;
workspace: IWorkspaceLite;
comment: string;
role: TUserPermissions;
preferences: ProjectPreferences;
view_props: IProjectViewProps;
default_props: IProjectViewProps;
created_at: Date;
updated_at: Date;
created_by: string;
updated_by: string;
}
export interface IProjectMembership {
id: string;
member: string;
role: TUserPermissions | EUserProjectRoles;
created_at: string;
} & (
| {
id: string;
original_role: EUserProjectRoles;
}
| {
id: null;
original_role: null;
}
);
role: TUserPermissions;
}
export interface IProjectBulkAddFormData {
members: { role: TUserPermissions | EUserProjectRoles; member_id: string }[];
-3
View File
@@ -12,7 +12,6 @@ export interface IUserLite {
id: string;
is_bot: boolean;
last_name: string;
joining_date?: string;
}
export interface IUser extends IUserLite {
// only for uploading the cover image
@@ -79,8 +78,6 @@ export interface IUserSettings {
workspace: {
last_workspace_id: string | undefined;
last_workspace_slug: string | undefined;
last_workspace_name: string | undefined;
last_workspace_logo: string | undefined;
fallback_workspace_id: string | undefined;
fallback_workspace_slug: string | undefined;
invites: number | undefined;
+2 -2
View File
@@ -1,4 +1,4 @@
import type { ICycle, TProjectMembership, IUser, IUserLite, IWorkspaceViewProps, TPaginationInfo } from "@plane/types";
import type { ICycle, IProjectMember, IUser, IUserLite, IWorkspaceViewProps, TPaginationInfo } from "@plane/types";
import { EUserWorkspaceRoles } from "@plane/constants"; // TODO: check if importing this over here causes circular dependency
import { TUserPermissions } from "./enums";
@@ -93,7 +93,7 @@ export interface IWorkspaceMemberMe {
export interface ILastActiveWorkspaceDetails {
workspace_details: IWorkspace;
project_details?: TProjectMembership[];
project_details?: IProjectMember[];
}
export interface IWorkspaceDefaultSearchResult {
+1 -1
View File
@@ -71,7 +71,7 @@
"postcss-cli": "^11.0.0",
"postcss-nested": "^6.0.1",
"storybook": "^8.1.1",
"tsup": "8.4.0",
"tsup": "^8.5.0",
"typescript": "5.3.3"
}
}
+1 -1
View File
@@ -160,7 +160,7 @@ export const Avatar: React.FC<Props> = (props) => {
color: fallbackTextColor ?? "#ffffff",
}}
>
{name?.[0]?.toUpperCase() ?? fallbackText ?? "?"}
{name ? name[0].toUpperCase() : fallbackText ?? "?"}
</div>
)}
</div>
+1 -5
View File
@@ -14,7 +14,6 @@ import { ICustomMenuDropdownProps, ICustomMenuItemProps } from "./helper";
const CustomMenu = (props: ICustomMenuDropdownProps) => {
const {
ariaLabel,
buttonClassName = "",
customButtonClassName = "",
customButtonTabIndex = 0,
@@ -76,7 +75,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
e.stopPropagation();
e.preventDefault();
isOpen ? closeDropdown() : openDropdown();
menuButtonOnClick?.();
if (menuButtonOnClick) menuButtonOnClick();
};
const handleMouseEnter = () => {
@@ -148,7 +147,6 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
className={customButtonClassName}
tabIndex={customButtonTabIndex}
disabled={disabled}
aria-label={ariaLabel}
>
{customButton}
</button>
@@ -166,7 +164,6 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
tabIndex={customButtonTabIndex}
aria-label={ariaLabel}
>
<MoreHorizontal className={`h-3.5 w-3.5 ${verticalEllipsis ? "rotate-90" : ""}`} />
</button>
@@ -186,7 +183,6 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
onClick={handleMenuButtonClick}
tabIndex={customButtonTabIndex}
disabled={disabled}
aria-label={ariaLabel}
>
{label}
{!noChevron && <ChevronDown className="h-3.5 w-3.5" />}
-1
View File
@@ -32,7 +32,6 @@ export interface ICustomMenuDropdownProps extends IDropdownProps {
closeOnSelect?: boolean;
portalElement?: Element | null;
openOnHover?: boolean;
ariaLabel?: string;
}
export interface ICustomSelectProps extends IDropdownProps {
+1 -1
View File
@@ -29,7 +29,7 @@
"@types/node": "^22.5.4",
"@types/react": "^18.3.11",
"@types/zxcvbn": "^4.4.5",
"tsup": "8.4.0",
"tsup": "^8.5.0",
"typescript": "^5.3.3"
}
}
+6 -4
View File
@@ -1,17 +1,19 @@
export * from "./array";
export * from "./attachment";
export * from "./auth";
export * from "./datetime";
export * from "./color";
export * from "./common";
export * from "./datetime";
export * from "./emoji";
export * from "./file";
export * from "./get-icon-for-link";
export * from "./issue";
export * from "./permission";
export * from "./state";
export * from "./string";
export * from "./subscription";
export * from "./theme";
export * from "./workspace";
export * from "./work-item";
export * from "./workspace";
export * from "./get-icon-for-link";
export * from "./subscription";
-13
View File
@@ -1,13 +0,0 @@
import { EUserPermissions, EUserProjectRoles, EUserWorkspaceRoles } from "@plane/constants";
type TSupportedRole = EUserPermissions | EUserProjectRoles | EUserWorkspaceRoles;
/**
* @description Returns the highest role from an array of supported roles
* @param { TSupportedRole[] } roles
* @returns { TSupportedRole | undefined }
*/
export const getHighestRole = <T extends TSupportedRole>(roles: T[]): T | undefined => {
if (!roles || roles.length === 0) return undefined;
return roles.reduce((highest, current) => (current > highest ? current : highest));
};
-1
View File
@@ -29,7 +29,6 @@ export const getReadOnlyEditorFileHandlers = (args: Pick<TArgs, "anchor" | "work
const { anchor, workspaceId } = args;
return {
checkIfAssetExists: async () => true,
getAssetSrc: async (path) => {
if (!path) return "";
if (path?.startsWith("http")) {
+1 -1
View File
@@ -37,7 +37,7 @@
"mobx": "^6.10.0",
"mobx-react": "^9.1.1",
"mobx-utils": "^6.0.8",
"next": "^14.2.29",
"next": "^14.2.28",
"next-themes": "^0.2.1",
"nprogress": "^0.2.0",
"react": "^18.3.1",
@@ -96,7 +96,7 @@ export const ExtendedAppSidebar = observer(() => {
useExtendedSidebarOutsideClickDetector(
extendedSidebarRef,
() => toggleExtendedSidebar(true),
() => toggleExtendedSidebar(false),
"extended-sidebar-toggle"
);
@@ -106,8 +106,8 @@ export const ExtendedAppSidebar = observer(() => {
className={cn(
"absolute top-0 h-full z-[19] flex flex-col gap-0.5 w-[300px] transform transition-all duration-300 ease-in-out bg-custom-sidebar-background-100 border-r border-custom-sidebar-border-200 p-4 shadow-md pb-6",
{
"-translate-x-full opacity-0 pointer-events-none": extendedSidebarCollapsed,
"translate-x-0 opacity-100 pointer-events-auto": !extendedSidebarCollapsed,
"translate-x-0 opacity-100 pointer-events-auto": extendedSidebarCollapsed,
"-translate-x-full opacity-0 pointer-events-none": !extendedSidebarCollapsed,
"left-[70px]": sidebarCollapsed,
"left-[250px]": !sidebarCollapsed,
}
@@ -69,7 +69,7 @@ const ProjectCyclesPage = observer(() => {
primaryButton={{
text: t("disabled_project.empty_state.cycle.primary_button.text"),
onClick: () => {
router.push(`/${workspaceSlug}/settings/projects/${projectId}/features`);
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
},
disabled: !hasAdminLevelPermission,
}}
@@ -42,7 +42,7 @@ const ProjectInboxPage = observer(() => {
primaryButton={{
text: t("disabled_project.empty_state.inbox.primary_button.text"),
onClick: () => {
router.push(`/${workspaceSlug}/settings/projects/${projectId}/features`);
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
},
disabled: !canPerformEmptyStateActions,
}}
@@ -61,7 +61,7 @@ const ProjectModulesPage = observer(() => {
primaryButton={{
text: t("disabled_project.empty_state.module.primary_button.text"),
onClick: () => {
router.push(`/${workspaceSlug}/settings/projects/${projectId}/features`);
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
},
disabled: !canPerformEmptyStateActions,
}}
@@ -54,7 +54,7 @@ const ProjectPagesPage = observer(() => {
primaryButton={{
text: t("disabled_project.empty_state.page.primary_button.text"),
onClick: () => {
router.push(`/${workspaceSlug}/settings/projects/${projectId}/features`);
router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`);
},
disabled: !canPerformEmptyStateActions,
}}
@@ -13,7 +13,6 @@ import { NotAuthorizedView } from "@/components/auth-screens";
import { AutoArchiveAutomation, AutoCloseAutomation } from "@/components/automation";
import { PageHead } from "@/components/core";
// hooks
import { SettingsContentWrapper, SettingsHeading } from "@/components/settings";
import { useProject, useUserPermissions } from "@/hooks/store";
const AutomationSettingsPage = observer(() => {
@@ -44,21 +43,20 @@ const AutomationSettingsPage = observer(() => {
const pageTitle = projectDetails?.name ? `${projectDetails?.name} - Automations` : undefined;
if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
return <NotAuthorizedView section="settings" isProjectView />;
}
return (
<SettingsContentWrapper>
<>
<PageHead title={pageTitle} />
<section className={`w-full ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
<SettingsHeading
title={t("project_settings.automations.heading")}
description={t("project_settings.automations.description")}
/>
<section className={`w-full overflow-y-auto ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
<div className="flex flex-col items-start border-b border-custom-border-100 pb-3.5">
<h3 className="text-xl font-medium leading-normal">{t("project_settings.automations.label")}</h3>
</div>
<AutoArchiveAutomation handleChange={handleChange} />
<AutoCloseAutomation handleChange={handleChange} />
</section>
</SettingsContentWrapper>
</>
);
});
@@ -8,7 +8,6 @@ import { NotAuthorizedView } from "@/components/auth-screens";
import { PageHead } from "@/components/core";
import { EstimateRoot } from "@/components/estimates";
// hooks
import { SettingsContentWrapper } from "@/components/settings";
import { useProject, useUserPermissions } from "@/hooks/store";
const EstimatesSettingsPage = observer(() => {
@@ -24,20 +23,22 @@ const EstimatesSettingsPage = observer(() => {
if (!workspaceSlug || !projectId) return <></>;
if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
return <NotAuthorizedView section="settings" isProjectView />;
}
return (
<SettingsContentWrapper>
<>
<PageHead title={pageTitle} />
<div className={`w-full ${canPerformProjectAdminActions ? "" : "pointer-events-none opacity-60"}`}>
<div
className={`w-full overflow-y-auto ${canPerformProjectAdminActions ? "" : "pointer-events-none opacity-60"}`}
>
<EstimateRoot
workspaceSlug={workspaceSlug?.toString()}
projectId={projectId?.toString()}
isAdmin={canPerformProjectAdminActions}
/>
</div>
</SettingsContentWrapper>
</>
);
});
@@ -8,7 +8,6 @@ import { NotAuthorizedView } from "@/components/auth-screens";
import { PageHead } from "@/components/core";
import { ProjectFeaturesList } from "@/components/project";
// hooks
import { SettingsContentWrapper } from "@/components/settings";
import { useProject, useUserPermissions } from "@/hooks/store";
const FeaturesSettingsPage = observer(() => {
@@ -24,20 +23,20 @@ const FeaturesSettingsPage = observer(() => {
if (!workspaceSlug || !projectId) return null;
if (workspaceUserInfo && !canPerformProjectAdminActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
return <NotAuthorizedView section="settings" isProjectView />;
}
return (
<SettingsContentWrapper>
<>
<PageHead title={pageTitle} />
<section className={`w-full ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
<section className={`w-full overflow-y-auto ${canPerformProjectAdminActions ? "" : "opacity-60"}`}>
<ProjectFeaturesList
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
isAdmin={canPerformProjectAdminActions}
/>
</section>
</SettingsContentWrapper>
</>
);
});
@@ -10,7 +10,6 @@ import { NotAuthorizedView } from "@/components/auth-screens";
import { PageHead } from "@/components/core";
import { ProjectSettingsLabelList } from "@/components/labels";
// hooks
import { SettingsContentWrapper } from "@/components/settings";
import { useProject, useUserPermissions } from "@/hooks/store";
const LabelsSettingsPage = observer(() => {
@@ -39,19 +38,19 @@ const LabelsSettingsPage = observer(() => {
element,
})
);
}, []);
}, [scrollableContainerRef?.current]);
if (workspaceUserInfo && !canPerformProjectMemberActions) {
return <NotAuthorizedView section="settings" isProjectView className="h-auto" />;
return <NotAuthorizedView section="settings" isProjectView />;
}
return (
<SettingsContentWrapper>
<>
<PageHead title={pageTitle} />
<div ref={scrollableContainerRef} className="h-full w-full gap-10">
<div ref={scrollableContainerRef} className="h-full w-full gap-10 overflow-y-auto">
<ProjectSettingsLabelList />
</div>
</SettingsContentWrapper>
</>
);
});

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