Compare commits
90
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12f5be7c8c | ||
|
|
9cdfb2224a | ||
|
|
ae0867e727 | ||
|
|
8129f5f969 | ||
|
|
16652a6b4c | ||
|
|
dacc9178ae | ||
|
|
08f47b55e1 | ||
|
|
89b8cdbe6e | ||
|
|
53e6a62a12 | ||
|
|
b3a9670225 | ||
|
|
51d76f1b0b | ||
|
|
75f89c4c12 | ||
|
|
ace3df4bc5 | ||
|
|
cb658c4b98 | ||
|
|
825855153e | ||
|
|
12c3e91c4b | ||
|
|
0983e5f44d | ||
|
|
2014400bed | ||
|
|
dffcc6dc10 | ||
|
|
640b23fb1b | ||
|
|
e13d8aa4b3 | ||
|
|
cf595de7c7 | ||
|
|
0fa9c8b015 | ||
|
|
6fe0415d66 | ||
|
|
ebc2bdcd3a | ||
|
|
11b222ece8 | ||
|
|
c1a078ef3f | ||
|
|
dc6660db78 | ||
|
|
ad11a34efc | ||
|
|
9c28db8b7b | ||
|
|
32d5fea3d3 | ||
|
|
f188542383 | ||
|
|
0a73fa64ee | ||
|
|
6adc721b34 | ||
|
|
531748dcc3 | ||
|
|
cb6d9397cf | ||
|
|
92559cc37b | ||
|
|
831c255184 | ||
|
|
aef81e2681 | ||
|
|
9cc484551e | ||
|
|
cb21fe4ea2 | ||
|
|
5cdf1a6351 | ||
|
|
c669643198 | ||
|
|
823885c872 | ||
|
|
9965f48ba7 | ||
|
|
d15d7549f7 | ||
|
|
8fcffd2338 | ||
|
|
07e937cd8e | ||
|
|
1f1b421735 | ||
|
|
5a43ec8411 | ||
|
|
c86e7e02bc | ||
|
|
d91d7a2f60 | ||
|
|
b3b285b1e5 | ||
|
|
11debee402 | ||
|
|
1608e4f122 | ||
|
|
edeeee1227 | ||
|
|
9ff238816b | ||
|
|
6bd5caf008 | ||
|
|
c021aff58f | ||
|
|
683be55883 | ||
|
|
970ce8cf26 | ||
|
|
cbbe1a4e4d | ||
|
|
6a74677cc9 | ||
|
|
f6ea4f931d | ||
|
|
950fcfdb40 | ||
|
|
053c895120 | ||
|
|
245167e8aa | ||
|
|
6be3f0ea73 | ||
|
|
14d2d69120 | ||
|
|
570a9e319e | ||
|
|
469a027bb6 | ||
|
|
8c99a7df88 | ||
|
|
f34f078bd2 | ||
|
|
0fe2549bc6 | ||
|
|
118964de01 | ||
|
|
9f37f1ef0e | ||
|
|
986f29d1f2 | ||
|
|
1113f9fc19 | ||
|
|
ef3ec7274c | ||
|
|
a0a45b7916 | ||
|
|
2792d48288 | ||
|
|
b2ccca0567 | ||
|
|
2e822b38e4 | ||
|
|
e570fe404f | ||
|
|
48b613ae66 | ||
|
|
461e099bbc | ||
|
|
45e25ce18b | ||
|
|
4d88dbaf49 | ||
|
+4 |
e61ff879c4 | ||
|
|
adeb7d977d |
@@ -290,5 +290,6 @@ jobs:
|
||||
${{ github.workspace }}/deploy/selfhost/setup.sh
|
||||
${{ github.workspace }}/deploy/selfhost/swarm.sh
|
||||
${{ github.workspace }}/deploy/selfhost/restore.sh
|
||||
${{ github.workspace }}/deploy/selfhost/restore-airgapped.sh
|
||||
${{ github.workspace }}/deploy/selfhost/docker-compose.yml
|
||||
${{ github.workspace }}/deploy/selfhost/variables.env
|
||||
|
||||
+3
-3
@@ -69,14 +69,14 @@ chmod +x setup.sh
|
||||
docker compose -f docker-compose-local.yml up
|
||||
```
|
||||
|
||||
5. Start web apps:
|
||||
4. Start web apps:
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
6. Open your browser to http://localhost:3001/god-mode/ and register yourself as instance admin
|
||||
7. Open up your browser to http://localhost:3000 then log in using the same credentials from the previous step
|
||||
5. Open your browser to http://localhost:3001/god-mode/ and register yourself as instance admin
|
||||
6. Open up your browser to http://localhost:3000 then log in using the same credentials from the previous step
|
||||
|
||||
That’s it! You’re all set to begin coding. Remember to refresh your browser if changes don’t auto-reload. Happy contributing! 🎉
|
||||
|
||||
|
||||
@@ -67,9 +67,8 @@ export const InstanceHeader: FC = observer(() => {
|
||||
{breadcrumbItems.length >= 0 && (
|
||||
<div>
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={
|
||||
<Breadcrumbs.Item
|
||||
component={
|
||||
<BreadcrumbLink
|
||||
href="/general/"
|
||||
label="Settings"
|
||||
@@ -80,10 +79,9 @@ export const InstanceHeader: FC = observer(() => {
|
||||
{breadcrumbItems.map(
|
||||
(item) =>
|
||||
item.title && (
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
<Breadcrumbs.Item
|
||||
key={item.title}
|
||||
type="text"
|
||||
link={<BreadcrumbLink href={item.href} label={item.title} />}
|
||||
component={<BreadcrumbLink href={item.href} label={item.title} />}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { FC } from "react";
|
||||
import { Info, X } from "lucide-react";
|
||||
// plane constants
|
||||
import { TAuthErrorInfo } from "@plane/constants";
|
||||
import { TAdminAuthErrorInfo } from "@plane/constants";
|
||||
|
||||
type TAuthBanner = {
|
||||
bannerData: TAuthErrorInfo | undefined;
|
||||
handleBannerData?: (bannerData: TAuthErrorInfo | undefined) => void;
|
||||
bannerData: TAdminAuthErrorInfo | undefined;
|
||||
handleBannerData?: (bannerData: TAdminAuthErrorInfo | undefined) => void;
|
||||
};
|
||||
|
||||
export const AuthBanner: FC<TAuthBanner> = (props) => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FC, useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// plane internal packages
|
||||
import { API_BASE_URL, EAdminAuthErrorCodes, TAuthErrorInfo } from "@plane/constants";
|
||||
import { API_BASE_URL, EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane/constants";
|
||||
import { AuthService } from "@plane/services";
|
||||
import { Button, Input, Spinner } from "@plane/ui";
|
||||
// components
|
||||
@@ -54,7 +54,7 @@ export const InstanceSignInForm: FC = (props) => {
|
||||
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
|
||||
const [formData, setFormData] = useState<TFormData>(defaultFromData);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
|
||||
const [errorInfo, setErrorInfo] = useState<TAdminAuthErrorInfo | undefined>(undefined);
|
||||
|
||||
const handleFormChange = (key: keyof TFormData, value: string | boolean) =>
|
||||
setFormData((prev) => ({ ...prev, [key]: value }));
|
||||
|
||||
@@ -3,7 +3,7 @@ import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { KeyRound, Mails } from "lucide-react";
|
||||
// plane packages
|
||||
import { SUPPORT_EMAIL, EAdminAuthErrorCodes, TAuthErrorInfo } from "@plane/constants";
|
||||
import { SUPPORT_EMAIL, EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane/constants";
|
||||
import { TGetBaseAuthenticationModeProps, TInstanceAuthenticationModes } from "@plane/types";
|
||||
import { resolveGeneralTheme } from "@plane/utils";
|
||||
// components
|
||||
@@ -89,7 +89,7 @@ const errorCodeMessages: {
|
||||
export const authErrorHandler = (
|
||||
errorCode: EAdminAuthErrorCodes,
|
||||
email?: string | undefined
|
||||
): TAuthErrorInfo | undefined => {
|
||||
): TAdminAuthErrorInfo | undefined => {
|
||||
const bannerAlertErrorCodes = [
|
||||
EAdminAuthErrorCodes.ADMIN_ALREADY_EXIST,
|
||||
EAdminAuthErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "admin",
|
||||
"description": "Admin UI for Plane",
|
||||
"version": "0.26.0",
|
||||
"version": "0.26.1",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -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.30",
|
||||
"next-themes": "^0.2.1",
|
||||
"postcss": "^8.4.38",
|
||||
"react": "^18.3.1",
|
||||
@@ -50,6 +50,6 @@
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/zxcvbn": "^4.4.4",
|
||||
"typescript": "5.3.3"
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "plane-api",
|
||||
"version": "0.26.0",
|
||||
"version": "0.26.1",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"description": "API server powering Plane's backend"
|
||||
|
||||
@@ -58,7 +58,7 @@ from plane.bgtasks.storage_metadata_task import get_asset_object_metadata
|
||||
from .base import BaseAPIView
|
||||
from plane.utils.host import base_host
|
||||
from plane.bgtasks.webhook_task import model_activity
|
||||
|
||||
from plane.bgtasks.work_item_link_task import crawl_work_item_link_title
|
||||
|
||||
class WorkspaceIssueAPIEndpoint(BaseAPIView):
|
||||
"""
|
||||
@@ -692,6 +692,9 @@ class IssueLinkAPIEndpoint(BaseAPIView):
|
||||
serializer = IssueLinkSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(project_id=project_id, issue_id=issue_id)
|
||||
crawl_work_item_link_title.delay(
|
||||
serializer.data.get("id"), serializer.data.get("url")
|
||||
)
|
||||
|
||||
link = IssueLink.objects.get(pk=serializer.data["id"])
|
||||
link.created_by_id = request.data.get("created_by", request.user.id)
|
||||
@@ -719,6 +722,9 @@ class IssueLinkAPIEndpoint(BaseAPIView):
|
||||
serializer = IssueLinkSerializer(issue_link, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
crawl_work_item_link_title.delay(
|
||||
serializer.data.get("id"), serializer.data.get("url")
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="link.activity.updated",
|
||||
requested_data=requested_data,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Third party imports
|
||||
from rest_framework import serializers
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
# Module imports
|
||||
from .base import BaseSerializer, DynamicBaseSerializer
|
||||
@@ -198,6 +196,7 @@ class WorkspaceUserLinkSerializer(BaseSerializer):
|
||||
|
||||
class IssueRecentVisitSerializer(serializers.ModelSerializer):
|
||||
project_identifier = serializers.SerializerMethodField()
|
||||
assignees = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
@@ -215,9 +214,15 @@ class IssueRecentVisitSerializer(serializers.ModelSerializer):
|
||||
|
||||
def get_project_identifier(self, obj):
|
||||
project = obj.project
|
||||
|
||||
return project.identifier if project else None
|
||||
|
||||
def get_assignees(self, obj):
|
||||
return list(
|
||||
obj.assignees.filter(issue_assignee__deleted_at__isnull=True).values_list(
|
||||
"id", flat=True
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ProjectRecentVisitSerializer(serializers.ModelSerializer):
|
||||
project_members = serializers.SerializerMethodField()
|
||||
|
||||
@@ -13,6 +13,8 @@ from plane.app.views import (
|
||||
ProjectAssetEndpoint,
|
||||
ProjectBulkAssetEndpoint,
|
||||
AssetCheckEndpoint,
|
||||
WorkspaceAssetDownloadEndpoint,
|
||||
ProjectAssetDownloadEndpoint,
|
||||
)
|
||||
|
||||
|
||||
@@ -89,4 +91,14 @@ urlpatterns = [
|
||||
AssetCheckEndpoint.as_view(),
|
||||
name="asset-check",
|
||||
),
|
||||
path(
|
||||
"assets/v2/workspaces/<str:slug>/download/<uuid:asset_id>/",
|
||||
WorkspaceAssetDownloadEndpoint.as_view(),
|
||||
name="workspace-asset-download",
|
||||
),
|
||||
path(
|
||||
"assets/v2/workspaces/<str:slug>/projects/<uuid:project_id>/download/<uuid:asset_id>/",
|
||||
ProjectAssetDownloadEndpoint.as_view(),
|
||||
name="project-asset-download",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -107,6 +107,8 @@ from .asset.v2 import (
|
||||
ProjectAssetEndpoint,
|
||||
ProjectBulkAssetEndpoint,
|
||||
AssetCheckEndpoint,
|
||||
WorkspaceAssetDownloadEndpoint,
|
||||
ProjectAssetDownloadEndpoint,
|
||||
)
|
||||
from .issue.base import (
|
||||
IssueListEndpoint,
|
||||
|
||||
@@ -718,3 +718,56 @@ class AssetCheckEndpoint(BaseAPIView):
|
||||
id=asset_id, workspace__slug=slug, deleted_at__isnull=True
|
||||
).exists()
|
||||
return Response({"exists": asset}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class WorkspaceAssetDownloadEndpoint(BaseAPIView):
|
||||
"""Endpoint to generate a download link for an asset with content-disposition=attachment."""
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def get(self, request, slug, asset_id):
|
||||
try:
|
||||
asset = FileAsset.objects.get(
|
||||
id=asset_id,
|
||||
workspace__slug=slug,
|
||||
is_uploaded=True,
|
||||
)
|
||||
except FileAsset.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "The requested asset could not be found."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
storage = S3Storage(request=request)
|
||||
signed_url = storage.generate_presigned_url(
|
||||
object_name=asset.asset.name,
|
||||
disposition=f"attachment; filename={asset.asset.name}",
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(signed_url)
|
||||
|
||||
|
||||
class ProjectAssetDownloadEndpoint(BaseAPIView):
|
||||
"""Endpoint to generate a download link for an asset with content-disposition=attachment."""
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="PROJECT")
|
||||
def get(self, request, slug, project_id, asset_id):
|
||||
try:
|
||||
asset = FileAsset.objects.get(
|
||||
id=asset_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
is_uploaded=True,
|
||||
)
|
||||
except FileAsset.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "The requested asset could not be found."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
storage = S3Storage(request=request)
|
||||
signed_url = storage.generate_presigned_url(
|
||||
object_name=asset.asset.name,
|
||||
disposition=f"attachment; filename={asset.asset.name}",
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(signed_url)
|
||||
|
||||
@@ -45,7 +45,7 @@ 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(
|
||||
crawl_work_item_link_title.delay(
|
||||
serializer.data.get("id"), serializer.data.get("url")
|
||||
)
|
||||
issue_activity.delay(
|
||||
@@ -78,7 +78,7 @@ class IssueLinkViewSet(BaseViewSet):
|
||||
serializer = IssueLinkSerializer(issue_link, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
crawl_work_item_link_title(
|
||||
crawl_work_item_link_title.delay(
|
||||
serializer.data.get("id"), serializer.data.get("url")
|
||||
)
|
||||
|
||||
|
||||
@@ -341,7 +341,10 @@ class ProjectViewSet(BaseViewSet):
|
||||
except IntegrityError as e:
|
||||
if "already exists" in str(e):
|
||||
return Response(
|
||||
{"name": "The project name is already taken"},
|
||||
{
|
||||
"name": "The project name is already taken",
|
||||
"code": "PROJECT_NAME_ALREADY_EXIST",
|
||||
},
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
)
|
||||
except Workspace.DoesNotExist:
|
||||
@@ -350,7 +353,10 @@ class ProjectViewSet(BaseViewSet):
|
||||
)
|
||||
except serializers.ValidationError:
|
||||
return Response(
|
||||
{"identifier": "The project identifier is already taken"},
|
||||
{
|
||||
"identifier": "The project identifier is already taken",
|
||||
"code": "PROJECT_IDENTIFIER_ALREADY_EXIST",
|
||||
},
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
)
|
||||
|
||||
|
||||
@@ -168,6 +168,8 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
workspace__slug=slug,
|
||||
member__is_bot=False,
|
||||
is_active=True,
|
||||
member__member_workspace__workspace__slug=slug,
|
||||
member__member_workspace__is_active=True,
|
||||
).select_related("project", "member", "workspace")
|
||||
|
||||
serializer = ProjectMemberRoleSerializer(
|
||||
@@ -313,7 +315,11 @@ class UserProjectRolesEndpoint(BaseAPIView):
|
||||
|
||||
def get(self, request, slug):
|
||||
project_members = ProjectMember.objects.filter(
|
||||
workspace__slug=slug, member_id=request.user.id, is_active=True
|
||||
workspace__slug=slug,
|
||||
member_id=request.user.id,
|
||||
is_active=True,
|
||||
member__member_workspace__workspace__slug=slug,
|
||||
member__member_workspace__is_active=True,
|
||||
).values("project_id", "role")
|
||||
|
||||
project_members = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Django imports
|
||||
from django.db.models import Count, Q, OuterRef, Subquery, IntegerField
|
||||
from django.utils import timezone
|
||||
from django.db.models.functions import Coalesce
|
||||
|
||||
# Third party modules
|
||||
@@ -133,7 +134,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
# Deactivate the users from the projects where the user is part of
|
||||
_ = ProjectMember.objects.filter(
|
||||
workspace__slug=slug, member_id=workspace_member.member_id, is_active=True
|
||||
).update(is_active=False)
|
||||
).update(is_active=False, updated_at=timezone.now())
|
||||
|
||||
workspace_member.is_active = False
|
||||
workspace_member.save()
|
||||
@@ -194,7 +195,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
# # Deactivate the users from the projects where the user is part of
|
||||
_ = ProjectMember.objects.filter(
|
||||
workspace__slug=slug, member_id=workspace_member.member_id, is_active=True
|
||||
).update(is_active=False)
|
||||
).update(is_active=False, updated_at=timezone.now())
|
||||
|
||||
# # Deactivate the user
|
||||
workspace_member.is_active = False
|
||||
|
||||
@@ -284,6 +284,7 @@ def send_email_notification(
|
||||
"project": str(issue.project.name),
|
||||
"user_preference": f"{base_api}/profile/preferences/email",
|
||||
"comments": comments,
|
||||
"entity_type": "issue",
|
||||
}
|
||||
html_content = render_to_string(
|
||||
"emails/notifications/issue-updates.html", context
|
||||
|
||||
@@ -19,17 +19,6 @@ 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.
|
||||
@@ -57,17 +46,18 @@ def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]:
|
||||
"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)
|
||||
soup = None
|
||||
title = None
|
||||
|
||||
response.raise_for_status()
|
||||
try:
|
||||
response = requests.get(url, headers=headers, timeout=1)
|
||||
|
||||
# Parse HTML
|
||||
soup = BeautifulSoup(response.content, "html.parser")
|
||||
soup = BeautifulSoup(response.content, "html.parser")
|
||||
title_tag = soup.find("title")
|
||||
title = title_tag.get_text().strip() if title_tag else None
|
||||
|
||||
# Extract title
|
||||
title_tag = soup.find("title")
|
||||
title = title_tag.get_text().strip() if title_tag else None
|
||||
except requests.RequestException as e:
|
||||
logger.warning(f"Failed to fetch HTML for title: {str(e)}")
|
||||
|
||||
# Fetch and encode favicon
|
||||
favicon_base64 = fetch_and_encode_favicon(headers, soup, url)
|
||||
@@ -82,14 +72,6 @@ def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]:
|
||||
|
||||
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 {
|
||||
@@ -100,7 +82,7 @@ def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def find_favicon_url(soup: BeautifulSoup, base_url: str) -> Optional[str]:
|
||||
def find_favicon_url(soup: Optional[BeautifulSoup], base_url: str) -> Optional[str]:
|
||||
"""
|
||||
Find the favicon URL from HTML soup.
|
||||
|
||||
@@ -111,18 +93,20 @@ def find_favicon_url(soup: BeautifulSoup, base_url: str) -> Optional[str]:
|
||||
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"])
|
||||
if soup is not 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)
|
||||
@@ -131,7 +115,6 @@ def find_favicon_url(soup: BeautifulSoup, base_url: str) -> Optional[str]:
|
||||
# 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:
|
||||
@@ -142,8 +125,8 @@ def find_favicon_url(soup: BeautifulSoup, base_url: str) -> Optional[str]:
|
||||
|
||||
|
||||
def fetch_and_encode_favicon(
|
||||
headers: Dict[str, str], soup: BeautifulSoup, url: str
|
||||
) -> Optional[Dict[str, str]]:
|
||||
headers: Dict[str, str], soup: Optional[BeautifulSoup], url: str
|
||||
) -> Dict[str, Optional[str]]:
|
||||
"""
|
||||
Fetch favicon and encode it as base64.
|
||||
|
||||
@@ -162,8 +145,7 @@ def fetch_and_encode_favicon(
|
||||
"favicon_base64": f"data:image/svg+xml;base64,{DEFAULT_FAVICON}",
|
||||
}
|
||||
|
||||
response = requests.get(favicon_url, headers=headers, timeout=2)
|
||||
response.raise_for_status()
|
||||
response = requests.get(favicon_url, headers=headers, timeout=1)
|
||||
|
||||
# Get content type
|
||||
content_type = response.headers.get("content-type", "image/x-icon")
|
||||
@@ -183,3 +165,13 @@ def fetch_and_encode_favicon(
|
||||
"favicon_url": None,
|
||||
"favicon_base64": f"data:image/svg+xml;base64,{DEFAULT_FAVICON}",
|
||||
}
|
||||
|
||||
|
||||
@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()
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.2.21 on 2025-06-06 12:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0096_user_is_email_valid_user_masked_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='external_id',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='external_source',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
@@ -122,6 +122,9 @@ class Project(BaseModel):
|
||||
# timezone
|
||||
TIMEZONE_CHOICES = tuple(zip(pytz.all_timezones, pytz.all_timezones))
|
||||
timezone = models.CharField(max_length=255, default="UTC", choices=TIMEZONE_CHOICES)
|
||||
# external_id for imports
|
||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
|
||||
@property
|
||||
def cover_image_url(self):
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import pytest
|
||||
|
||||
from plane.db.models import (
|
||||
Workspace,
|
||||
Project,
|
||||
Issue,
|
||||
User,
|
||||
IssueAssignee,
|
||||
WorkspaceMember,
|
||||
ProjectMember,
|
||||
)
|
||||
from plane.app.serializers.workspace import IssueRecentVisitSerializer
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestIssueRecentVisitSerializer:
|
||||
"""Test the IssueRecentVisitSerializer"""
|
||||
|
||||
def test_issue_recent_visit_serializer_fields(self, db):
|
||||
"""Test that the serializer includes the correct fields"""
|
||||
|
||||
test_user_1 = User.objects.create(
|
||||
email="test_user_1@example.com", first_name="Test", last_name="User"
|
||||
)
|
||||
|
||||
# To test for deleted issue assignee
|
||||
test_user_2 = User.objects.create(
|
||||
email="test_user_2@example.com",
|
||||
first_name="Other",
|
||||
last_name="User",
|
||||
username="some user name",
|
||||
)
|
||||
|
||||
workspace = Workspace.objects.create(
|
||||
name="Test Workspace", slug="test-workspace", owner=test_user_1
|
||||
)
|
||||
|
||||
WorkspaceMember.objects.create(member=test_user_2, role=15, workspace=workspace)
|
||||
|
||||
project = Project.objects.create(
|
||||
name="Test Project", identifier="test-project", workspace=workspace
|
||||
)
|
||||
ProjectMember.objects.create(project=project, member=test_user_2)
|
||||
|
||||
issue = Issue.objects.create(
|
||||
name="Test Issue",
|
||||
workspace=workspace,
|
||||
project=project,
|
||||
)
|
||||
|
||||
IssueAssignee.objects.create(issue=issue, assignee=test_user_1, project=project)
|
||||
|
||||
# Deleted issue assignee
|
||||
IssueAssignee.objects.create(
|
||||
issue=issue,
|
||||
assignee=test_user_2,
|
||||
project=project,
|
||||
deleted_at=timezone.now(),
|
||||
)
|
||||
|
||||
serialized_data = IssueRecentVisitSerializer(
|
||||
issue,
|
||||
).data
|
||||
|
||||
# Check fields are present and correct
|
||||
assert "name" in serialized_data
|
||||
assert "assignees" in serialized_data
|
||||
assert "project_identifier" in serialized_data
|
||||
|
||||
assert serialized_data["name"] == "Test Issue"
|
||||
assert serialized_data["project_identifier"] == "TEST-PROJECT"
|
||||
|
||||
# Only including non-deleted issue assignees
|
||||
assert serialized_data["assignees"] == [test_user_1.id]
|
||||
@@ -1,7 +1,7 @@
|
||||
# base requirements
|
||||
|
||||
# django
|
||||
Django==4.2.21
|
||||
Django==4.2.22
|
||||
# rest framework
|
||||
djangorestframework==3.15.2
|
||||
# postgres
|
||||
|
||||
@@ -9,4 +9,4 @@ factory-boy==3.3.0
|
||||
freezegun==1.2.2
|
||||
coverage==7.2.7
|
||||
httpx==0.24.1
|
||||
requests==2.32.2
|
||||
requests==2.32.4
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Updates on issue</title>
|
||||
<title>Updates on {{entity_type}}</title>
|
||||
<style type="text/css" emogrify="no"> html { font-family: system-ui; } p, h1, h2, h3, h4, ol, ul { margin: 0; } h-full { height: 100%; } a:hover { color: #3358d4 !important; } </style>
|
||||
<style> *[class="gmail-fix"] { display: none !important; } </style>
|
||||
<style type="text/css" emogrify="no"> @media (max-width: 600px) { .gmx-killpill { content: " \03D1"; } } </style>
|
||||
@@ -37,7 +37,7 @@
|
||||
{% else %}
|
||||
<p style="font-size: 1rem;color: #1f2d5c; line-height: 28px"> {{summary}} <span style="font-size: 1rem; font-weight: 700; line-height: 28px"> {% if data|length > 0 %} {{ data.0.actor_detail.first_name}} {{data.0.actor_detail.last_name}} {% else %} {{ comments.0.actor_detail.first_name}} {{comments.0.actor_detail.last_name}} {% endif %} </span>and others. </p>
|
||||
{% endif %} <!-- {% if actors_involved == 1 %} {% if data|length > 0 and comments|length == 0 %} <p style="font-size: 1rem;color: #1f2d5c; line-height: 28px"> <span style="font-size: 1rem; font-weight: 700; line-height: 28px"> {{ data.0.actor_detail.first_name}} {{data.0.actor_detail.last_name }} </span> made {{total_updates}} {% if total_updates > 1 %}updates{% else %}update{% endif %} to the issue. </p> {% elif data|length == 0 and comments|length > 0 %} <p style="font-size: 1rem;color: #1f2d5c; line-height: 28px"> <span style="font-size: 1rem; font-weight: 700; line-height: 28px"> {{ comments.0.actor_detail.first_name}} {{comments.0.actor_detail.last_name }} </span> added {{total_comments}} new {% if total_comments > 1 %}comments{% else %}comment{% endif %}. </p> {% elif data|length > 0 and comments|length > 0 %} <p style="font-size: 1rem;color: #1f2d5c; line-height: 28px"> <span style="font-size: 1rem; font-weight: 700; line-height: 28px"> {{ data.0.actor_detail.first_name}} {{data.0.actor_detail.last_name }} </span> made {{total_updates}} {% if total_updates > 1 %}updates{% else %}update{% endif %} and added {{total_comments}} new {% if total_comments > 1 %}comments{% else %}comment{% endif %} on the issue. </p> {% endif %} {% else %} <p style="font-size: 1rem;color: #1f2d5c; line-height: 28px"> There are {{ total_updates }} new updates and {{total_comments}} new comments on the issue. </p> {% endif %} --> {% for update in data %} {% if update.changes.name %} <!-- Issue title updated -->
|
||||
<p style="font-size: 1rem; line-height: 28px; color: #1f2d5c"> The issue title has been updated to {{ issue.name}} </p>
|
||||
<p style="font-size: 1rem; line-height: 28px; color: #1f2d5c"> The {{entity_type}} title has been updated to {{ issue.name}} </p>
|
||||
{% endif %} <!-- Outer update Box start --> {% if data %}
|
||||
<div style=" background-color: #f7f9ff; border-radius: 8px; border-style: solid; border-width: 1px; border-color: #c1d0ff; padding: 20px; margin-top: 15px; max-width: 100%; " >
|
||||
<!-- Block Heading -->
|
||||
@@ -224,7 +224,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<a href="{{ issue_url }}" style="text-decoration: none;">
|
||||
<div style=" max-width: min-content; white-space: nowrap; background-color: #3e63dd; padding: 10px 15px; border: 1px solid #2f4ba8; border-radius: 4px; margin-top: 15px; cursor: pointer; font-size: 0.8rem; color: white; " > View issue </div>
|
||||
<div style=" max-width: min-content; white-space: nowrap; background-color: #3e63dd; padding: 10px 15px; border: 1px solid #2f4ba8; border-radius: 4px; margin-top: 15px; cursor: pointer; font-size: 0.8rem; color: white; " > View {{entity_type}} </div>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
@@ -232,7 +232,7 @@
|
||||
<tr>
|
||||
<td>
|
||||
<div style="font-size: 0.8rem; color: #1c2024">
|
||||
This email was sent to <a href="mailto:{{receiver.email}}" style="color: #3a5bc7; font-weight: 500; text-decoration: none" >{{ receiver.email }}.</a > If you'd rather not receive this kind of email, <a href="{{ issue_url }}" style="color: #3a5bc7; text-decoration: none" >you can unsubscribe to the issue</a > or <a href="{{ user_preference }}" style="color: #3a5bc7; text-decoration: none" >manage your email preferences</a >. <!-- Github | LinkedIn | Twitter -->
|
||||
This email was sent to <a href="mailto:{{receiver.email}}" style="color: #3a5bc7; font-weight: 500; text-decoration: none" >{{ receiver.email }}.</a > If you'd rather not receive this kind of email, <a href="{{ issue_url }}" style="color: #3a5bc7; text-decoration: none" >you can unsubscribe to the {{entity_type}}</a > or <a href="{{ user_preference }}" style="color: #3a5bc7; text-decoration: none" >manage your email preferences</a >. <!-- Github | LinkedIn | Twitter -->
|
||||
<div style="margin-top: 60px; float: right"> <a href="https://github.com/makeplane" target="_blank" style="margin-left: 10px; text-decoration: none" > <img src="https://creative-assets.mailinblue.com/editor/social-icons/rounded_colored/github_32px.png" width="25" height="25" border="0" style="display: inline-block" /> </a> <a href="https://www.linkedin.com/company/planepowers/" target="_blank" style="margin-left: 10px; text-decoration: none" > <img src="https://creative-assets.mailinblue.com/editor/social-icons/rounded_colored/linkedin_32px.png" width="25" height="25" border="0" style="display: inline-block" /> </a> <a href="https://twitter.com/planepowers" target="_blank" style="margin-left: 10px; text-decoration: none" > <img src="https://creative-assets.mailinblue.com/editor/social-icons/rounded_colored/twitter_32px.png" width="25" height="25" border="0" style="display: inline-block" /> </a> </div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -486,7 +486,7 @@ When you want to restore the previously backed-up data, follow the instructions
|
||||
1. Download the restore script using the command below. We suggest downloading it in the same folder as `setup.sh`.
|
||||
|
||||
```bash
|
||||
curl -fsSL -o restore.sh https://raw.githubusercontent.com/makeplane/plane/master/deploy/selfhost/restore.sh
|
||||
curl -fsSL -o restore.sh https://github.com/makeplane/plane/releases/latest/download/restore.sh
|
||||
chmod +x restore.sh
|
||||
```
|
||||
|
||||
@@ -529,6 +529,31 @@ When you want to restore the previously backed-up data, follow the instructions
|
||||
|
||||
---
|
||||
|
||||
### Restore for Commercial Air-Gapped (Docker Compose)
|
||||
|
||||
When you want to restore the previously backed-up data on Plane Commercial Air-Gapped version, follow the instructions below.
|
||||
|
||||
1. Download the restore script using the command below
|
||||
|
||||
```bash
|
||||
curl -fsSL -o restore-airgapped.sh https://github.com/makeplane/plane/releases/latest/download/restore-airgapped.sh
|
||||
chmod +x restore-airgapped.sh
|
||||
```
|
||||
|
||||
1. Copy the backup folder and the `restore-airgapped.sh` to `Commercial Airgapped Edition` server
|
||||
|
||||
1. Make sure that Plane Commercial (Airgapped) is extracted and ready to get started. In case it is running, you would need to stop that.
|
||||
|
||||
1. Execute the command below to restore your data.
|
||||
|
||||
```bash
|
||||
./restore-airgapped.sh <path to backup folder containing *.tar.gz files>
|
||||
```
|
||||
|
||||
1. After restoration, you are ready to start Plane Commercial (Airgapped) will all your previously saved data.
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><h2>Upgrading from v0.13.2 to v0.14.x</h2></summary>
|
||||
|
||||
|
||||
Executable
+144
@@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
+set -euo pipefail
|
||||
|
||||
function print_header() {
|
||||
clear
|
||||
|
||||
cat <<"EOF"
|
||||
--------------------------------------------
|
||||
____ _ /////////
|
||||
| _ \| | __ _ _ __ ___ /////////
|
||||
| |_) | |/ _` | '_ \ / _ \ ///// /////
|
||||
| __/| | (_| | | | | __/ ///// /////
|
||||
|_| |_|\__,_|_| |_|\___| ////
|
||||
////
|
||||
--------------------------------------------
|
||||
Project management tool from the future
|
||||
--------------------------------------------
|
||||
EOF
|
||||
}
|
||||
|
||||
function restoreData() {
|
||||
|
||||
echo ""
|
||||
echo "****************************************************"
|
||||
echo "We are about to restore your data from the backup files."
|
||||
echo "****************************************************"
|
||||
echo ""
|
||||
|
||||
# set the backup folder path
|
||||
BACKUP_FOLDER=${1}
|
||||
|
||||
if [ -z "$BACKUP_FOLDER" ]; then
|
||||
BACKUP_FOLDER="$PWD/backup"
|
||||
read -p "Enter the backup folder path [$BACKUP_FOLDER]: " BACKUP_FOLDER
|
||||
if [ -z "$BACKUP_FOLDER" ]; then
|
||||
BACKUP_FOLDER="$PWD/backup"
|
||||
fi
|
||||
fi
|
||||
|
||||
# check if the backup folder exists
|
||||
if [ ! -d "$BACKUP_FOLDER" ]; then
|
||||
echo "Error: Backup folder not found at $BACKUP_FOLDER"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if there are any .tar.gz files in the backup folder
|
||||
if ! ls "$BACKUP_FOLDER"/*.tar.gz 1> /dev/null 2>&1; then
|
||||
echo "Error: Backup folder does not contain .tar.gz files"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Using backup folder: $BACKUP_FOLDER"
|
||||
echo ""
|
||||
|
||||
# ask for current install path
|
||||
AIRGAPPED_INSTALL_PATH="$HOME/planeairgapped"
|
||||
read -p "Enter the airgapped instance install path [$AIRGAPPED_INSTALL_PATH]: " AIRGAPPED_INSTALL_PATH
|
||||
if [ -z "$AIRGAPPED_INSTALL_PATH" ]; then
|
||||
AIRGAPPED_INSTALL_PATH="$HOME/planeairgapped"
|
||||
fi
|
||||
|
||||
# check if the airgapped instance install path exists
|
||||
if [ ! -d "$AIRGAPPED_INSTALL_PATH" ]; then
|
||||
echo "Error: Airgapped instance install path not found at $AIRGAPPED_INSTALL_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Using airgapped instance install path: $AIRGAPPED_INSTALL_PATH"
|
||||
echo ""
|
||||
|
||||
# check if the docker-compose.yaml exists
|
||||
if [ ! -f "$AIRGAPPED_INSTALL_PATH/docker-compose.yml" ]; then
|
||||
echo "Error: docker-compose.yml not found at $AIRGAPPED_INSTALL_PATH/docker-compose.yml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local dockerServiceStatus
|
||||
if command -v jq &> /dev/null; then
|
||||
dockerServiceStatus=$($COMPOSE_CMD ls --filter name=plane-airgapped --format=json | jq -r .[0].Status)
|
||||
else
|
||||
dockerServiceStatus=$($COMPOSE_CMD ls --filter name=plane-airgapped | grep -o "running" | head -n 1)
|
||||
fi
|
||||
|
||||
if [[ $dockerServiceStatus == "running" ]]; then
|
||||
echo "Plane Airgapped is running. Please STOP the Plane Airgapped before restoring data."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_USER_ID=$(id -u)
|
||||
CURRENT_GROUP_ID=$(id -g)
|
||||
|
||||
# if the data folder not exists, create it
|
||||
if [ ! -d "$AIRGAPPED_INSTALL_PATH/data" ]; then
|
||||
mkdir -p "$AIRGAPPED_INSTALL_PATH/data"
|
||||
chown -R $CURRENT_USER_ID:$CURRENT_GROUP_ID "$AIRGAPPED_INSTALL_PATH/data"
|
||||
fi
|
||||
|
||||
for BACKUP_FILE in "$BACKUP_FOLDER/*.tar.gz"; do
|
||||
if [ -e "$BACKUP_FILE" ]; then
|
||||
|
||||
# get the basefilename without the extension
|
||||
BASE_FILE_NAME=$(basename "$BACKUP_FILE" ".tar.gz")
|
||||
|
||||
# extract the restoreFile to the airgapped instance install path
|
||||
echo "Restoring $BASE_FILE_NAME"
|
||||
rm -rf "$AIRGAPPED_INSTALL_PATH/data/$BASE_FILE_NAME" || true
|
||||
|
||||
tar -xvzf "$BACKUP_FILE" -C "$AIRGAPPED_INSTALL_PATH/data/"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to extract $BACKUP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
chown -R $CURRENT_USER_ID:$CURRENT_GROUP_ID "$AIRGAPPED_INSTALL_PATH/data/$BASE_FILE_NAME"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: Failed to change ownership of $AIRGAPPED_INSTALL_PATH/data/$BASE_FILE_NAME"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "No .tar.gz files found in the current directory."
|
||||
echo ""
|
||||
echo "Please provide the path to the backup file."
|
||||
echo ""
|
||||
echo "Usage: $0 /path/to/backup"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Restore completed successfully."
|
||||
echo ""
|
||||
}
|
||||
|
||||
# if docker-compose is installed
|
||||
if command -v docker-compose &> /dev/null
|
||||
then
|
||||
COMPOSE_CMD="docker-compose"
|
||||
else
|
||||
COMPOSE_CMD="docker compose"
|
||||
fi
|
||||
|
||||
print_header
|
||||
restoreData "$@"
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "live",
|
||||
"version": "0.26.0",
|
||||
"version": "0.26.1",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "A realtime collaborative server powers Plane's rich text editor",
|
||||
"main": "./src/server.ts",
|
||||
@@ -58,6 +58,6 @@
|
||||
"nodemon": "^3.1.7",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsup": "8.4.0",
|
||||
"typescript": "5.3.3"
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -2,7 +2,7 @@
|
||||
"name": "plane",
|
||||
"description": "Open-source project management that unlocks customer value",
|
||||
"repository": "https://github.com/makeplane/plane.git",
|
||||
"version": "0.26.0",
|
||||
"version": "0.26.1",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
@@ -24,14 +24,16 @@
|
||||
"devDependencies": {
|
||||
"prettier": "latest",
|
||||
"prettier-plugin-tailwindcss": "^0.5.4",
|
||||
"turbo": "^2.5.3"
|
||||
"turbo": "^2.5.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"brace-expansion": "2.0.2",
|
||||
"nanoid": "3.3.8",
|
||||
"esbuild": "0.25.0",
|
||||
"@babel/helpers": "7.26.10",
|
||||
"@babel/runtime": "7.26.10",
|
||||
"chokidar": "3.6.0"
|
||||
"chokidar": "3.6.0",
|
||||
"tar-fs": "3.0.9"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/constants",
|
||||
"version": "0.26.0",
|
||||
"version": "0.26.1",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"license": "AGPL-3.0"
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import { TAnalyticsTabsV2Base } from "@plane/types";
|
||||
import { ChartXAxisProperty, ChartYAxisMetric } from "../chart";
|
||||
|
||||
export const insightsFields: Record<TAnalyticsTabsV2Base, string[]> = {
|
||||
overview: [
|
||||
"total_users",
|
||||
"total_admins",
|
||||
"total_members",
|
||||
"total_guests",
|
||||
"total_projects",
|
||||
"total_work_items",
|
||||
"total_cycles",
|
||||
"total_intake",
|
||||
],
|
||||
"work-items": [
|
||||
"total_work_items",
|
||||
"started_work_items",
|
||||
"backlog_work_items",
|
||||
"un_started_work_items",
|
||||
"completed_work_items",
|
||||
],
|
||||
};
|
||||
|
||||
export const ANALYTICS_V2_DURATION_FILTER_OPTIONS = [
|
||||
{
|
||||
name: "Yesterday",
|
||||
value: "yesterday",
|
||||
},
|
||||
{
|
||||
name: "Last 7 days",
|
||||
value: "last_7_days",
|
||||
},
|
||||
{
|
||||
name: "Last 30 days",
|
||||
value: "last_30_days",
|
||||
},
|
||||
{
|
||||
name: "Last 3 months",
|
||||
value: "last_3_months",
|
||||
},
|
||||
];
|
||||
|
||||
export const ANALYTICS_V2_X_AXIS_VALUES: { value: ChartXAxisProperty; label: string }[] = [
|
||||
{
|
||||
value: ChartXAxisProperty.STATES,
|
||||
label: "State name",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.STATE_GROUPS,
|
||||
label: "State group",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.PRIORITY,
|
||||
label: "Priority",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.LABELS,
|
||||
label: "Label",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.ASSIGNEES,
|
||||
label: "Assignee",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.ESTIMATE_POINTS,
|
||||
label: "Estimate point",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.CYCLES,
|
||||
label: "Cycle",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.MODULES,
|
||||
label: "Module",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.COMPLETED_AT,
|
||||
label: "Completed date",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.TARGET_DATE,
|
||||
label: "Due date",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.START_DATE,
|
||||
label: "Start date",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.CREATED_AT,
|
||||
label: "Created date",
|
||||
},
|
||||
];
|
||||
|
||||
export const ANALYTICS_V2_Y_AXIS_VALUES: { value: ChartYAxisMetric; label: string }[] = [
|
||||
{
|
||||
value: ChartYAxisMetric.WORK_ITEM_COUNT,
|
||||
label: "Work item",
|
||||
},
|
||||
{
|
||||
value: ChartYAxisMetric.ESTIMATE_POINT_COUNT,
|
||||
label: "Estimate",
|
||||
},
|
||||
];
|
||||
|
||||
export const ANALYTICS_V2_DATE_KEYS = ["completed_at", "target_date", "start_date", "created_at"];
|
||||
@@ -1,81 +0,0 @@
|
||||
// types
|
||||
import { TXAxisValues, TYAxisValues } from "@plane/types";
|
||||
|
||||
export const ANALYTICS_TABS = [
|
||||
{
|
||||
key: "scope_and_demand",
|
||||
i18n_title: "workspace_analytics.tabs.scope_and_demand",
|
||||
},
|
||||
{ key: "custom", i18n_title: "workspace_analytics.tabs.custom" },
|
||||
];
|
||||
|
||||
export const ANALYTICS_X_AXIS_VALUES: { value: TXAxisValues; label: string }[] =
|
||||
[
|
||||
{
|
||||
value: "state_id",
|
||||
label: "State name",
|
||||
},
|
||||
{
|
||||
value: "state__group",
|
||||
label: "State group",
|
||||
},
|
||||
{
|
||||
value: "priority",
|
||||
label: "Priority",
|
||||
},
|
||||
{
|
||||
value: "labels__id",
|
||||
label: "Label",
|
||||
},
|
||||
{
|
||||
value: "assignees__id",
|
||||
label: "Assignee",
|
||||
},
|
||||
{
|
||||
value: "estimate_point__value",
|
||||
label: "Estimate point",
|
||||
},
|
||||
{
|
||||
value: "issue_cycle__cycle_id",
|
||||
label: "Cycle",
|
||||
},
|
||||
{
|
||||
value: "issue_module__module_id",
|
||||
label: "Module",
|
||||
},
|
||||
{
|
||||
value: "completed_at",
|
||||
label: "Completed date",
|
||||
},
|
||||
{
|
||||
value: "target_date",
|
||||
label: "Due date",
|
||||
},
|
||||
{
|
||||
value: "start_date",
|
||||
label: "Start date",
|
||||
},
|
||||
{
|
||||
value: "created_at",
|
||||
label: "Created date",
|
||||
},
|
||||
];
|
||||
|
||||
export const ANALYTICS_Y_AXIS_VALUES: { value: TYAxisValues; label: string }[] =
|
||||
[
|
||||
{
|
||||
value: "issue_count",
|
||||
label: "Work item Count",
|
||||
},
|
||||
{
|
||||
value: "estimate",
|
||||
label: "Estimate",
|
||||
},
|
||||
];
|
||||
|
||||
export const ANALYTICS_DATE_KEYS = [
|
||||
"completed_at",
|
||||
"target_date",
|
||||
"start_date",
|
||||
"created_at",
|
||||
];
|
||||
@@ -0,0 +1,178 @@
|
||||
import { TAnalyticsTabsBase } from "@plane/types";
|
||||
import { ChartXAxisProperty, ChartYAxisMetric } from "../chart";
|
||||
|
||||
export interface IInsightField {
|
||||
key: string;
|
||||
i18nKey: string;
|
||||
i18nProps?: {
|
||||
entity?: string;
|
||||
entityPlural?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export const ANALYTICS_INSIGHTS_FIELDS: Record<TAnalyticsTabsBase, IInsightField[]> = {
|
||||
overview: [
|
||||
{
|
||||
key: "total_users",
|
||||
i18nKey: "workspace_analytics.total",
|
||||
i18nProps: {
|
||||
entity: "common.users",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "total_admins",
|
||||
i18nKey: "workspace_analytics.total",
|
||||
i18nProps: {
|
||||
entity: "common.admins",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "total_members",
|
||||
i18nKey: "workspace_analytics.total",
|
||||
i18nProps: {
|
||||
entity: "common.members",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "total_guests",
|
||||
i18nKey: "workspace_analytics.total",
|
||||
i18nProps: {
|
||||
entity: "common.guests",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "total_projects",
|
||||
i18nKey: "workspace_analytics.total",
|
||||
i18nProps: {
|
||||
entity: "common.projects",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "total_work_items",
|
||||
i18nKey: "workspace_analytics.total",
|
||||
i18nProps: {
|
||||
entity: "common.work_items",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "total_cycles",
|
||||
i18nKey: "workspace_analytics.total",
|
||||
i18nProps: {
|
||||
entity: "common.cycles",
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "total_intake",
|
||||
i18nKey: "workspace_analytics.total",
|
||||
i18nProps: {
|
||||
entity: "sidebar.intake",
|
||||
},
|
||||
},
|
||||
],
|
||||
"work-items": [
|
||||
{
|
||||
key: "total_work_items",
|
||||
i18nKey: "workspace_analytics.total",
|
||||
},
|
||||
{
|
||||
key: "started_work_items",
|
||||
i18nKey: "workspace_analytics.started_work_items",
|
||||
},
|
||||
{
|
||||
key: "backlog_work_items",
|
||||
i18nKey: "workspace_analytics.backlog_work_items",
|
||||
},
|
||||
{
|
||||
key: "un_started_work_items",
|
||||
i18nKey: "workspace_analytics.un_started_work_items",
|
||||
},
|
||||
{
|
||||
key: "completed_work_items",
|
||||
i18nKey: "workspace_analytics.completed_work_items",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const ANALYTICS_DURATION_FILTER_OPTIONS = [
|
||||
{
|
||||
name: "Yesterday",
|
||||
value: "yesterday",
|
||||
},
|
||||
{
|
||||
name: "Last 7 days",
|
||||
value: "last_7_days",
|
||||
},
|
||||
{
|
||||
name: "Last 30 days",
|
||||
value: "last_30_days",
|
||||
},
|
||||
{
|
||||
name: "Last 3 months",
|
||||
value: "last_3_months",
|
||||
},
|
||||
];
|
||||
|
||||
export const ANALYTICS_X_AXIS_VALUES: { value: ChartXAxisProperty; label: string }[] = [
|
||||
{
|
||||
value: ChartXAxisProperty.STATES,
|
||||
label: "State name",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.STATE_GROUPS,
|
||||
label: "State group",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.PRIORITY,
|
||||
label: "Priority",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.LABELS,
|
||||
label: "Label",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.ASSIGNEES,
|
||||
label: "Assignee",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.ESTIMATE_POINTS,
|
||||
label: "Estimate point",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.CYCLES,
|
||||
label: "Cycle",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.MODULES,
|
||||
label: "Module",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.COMPLETED_AT,
|
||||
label: "Completed date",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.TARGET_DATE,
|
||||
label: "Due date",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.START_DATE,
|
||||
label: "Start date",
|
||||
},
|
||||
{
|
||||
value: ChartXAxisProperty.CREATED_AT,
|
||||
label: "Created date",
|
||||
},
|
||||
];
|
||||
|
||||
export const ANALYTICS_Y_AXIS_VALUES: { value: ChartYAxisMetric; label: string }[] = [
|
||||
{
|
||||
value: ChartYAxisMetric.WORK_ITEM_COUNT,
|
||||
label: "Work item",
|
||||
},
|
||||
{
|
||||
value: ChartYAxisMetric.ESTIMATE_POINT_COUNT,
|
||||
label: "Estimate",
|
||||
},
|
||||
];
|
||||
|
||||
export const ANALYTICS_V2_DATE_KEYS = ["completed_at", "target_date", "start_date", "created_at"];
|
||||
@@ -69,7 +69,7 @@ export enum EErrorAlertType {
|
||||
|
||||
export type TAuthErrorInfo = {
|
||||
type: EErrorAlertType;
|
||||
code: EAdminAuthErrorCodes;
|
||||
code: EAuthErrorCodes;
|
||||
title: string;
|
||||
message: any;
|
||||
};
|
||||
@@ -87,6 +87,13 @@ export enum EAdminAuthErrorCodes {
|
||||
ADMIN_USER_DEACTIVATED = "5190",
|
||||
}
|
||||
|
||||
export type TAdminAuthErrorInfo = {
|
||||
type: EErrorAlertType;
|
||||
code: EAdminAuthErrorCodes;
|
||||
title: string;
|
||||
message: any;
|
||||
};
|
||||
|
||||
export enum EAuthErrorCodes {
|
||||
// Global
|
||||
INSTANCE_NOT_CONFIGURED = "5000",
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "";
|
||||
export const API_BASE_PATH = process.env.NEXT_PUBLIC_API_BASE_PATH || "/";
|
||||
export const API_BASE_PATH = process.env.NEXT_PUBLIC_API_BASE_PATH || "";
|
||||
export const API_URL = encodeURI(`${API_BASE_URL}${API_BASE_PATH}`);
|
||||
// God Mode Admin App Base Url
|
||||
export const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || "";
|
||||
export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "/";
|
||||
export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "";
|
||||
export const GOD_MODE_URL = encodeURI(`${ADMIN_BASE_URL}${ADMIN_BASE_PATH}`);
|
||||
// Publish App Base Url
|
||||
export const SPACE_BASE_URL = process.env.NEXT_PUBLIC_SPACE_BASE_URL || "";
|
||||
export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || "/";
|
||||
export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || "";
|
||||
export const SITES_URL = encodeURI(`${SPACE_BASE_URL}${SPACE_BASE_PATH}`);
|
||||
// Live App Base Url
|
||||
export const LIVE_BASE_URL = process.env.NEXT_PUBLIC_LIVE_BASE_URL || "";
|
||||
export const LIVE_BASE_PATH = process.env.NEXT_PUBLIC_LIVE_BASE_PATH || "/";
|
||||
export const LIVE_BASE_PATH = process.env.NEXT_PUBLIC_LIVE_BASE_PATH || "";
|
||||
export const LIVE_URL = encodeURI(`${LIVE_BASE_URL}${LIVE_BASE_PATH}`);
|
||||
// Web App Base Url
|
||||
export const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL || "";
|
||||
export const WEB_BASE_PATH = process.env.NEXT_PUBLIC_WEB_BASE_PATH || "/";
|
||||
export const WEB_BASE_PATH = process.env.NEXT_PUBLIC_WEB_BASE_PATH || "";
|
||||
export const WEB_URL = encodeURI(`${WEB_BASE_URL}${WEB_BASE_PATH}`);
|
||||
// plane website url
|
||||
export const WEBSITE_URL =
|
||||
process.env.NEXT_PUBLIC_WEBSITE_URL || "https://plane.so";
|
||||
export const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL || "https://plane.so";
|
||||
// support email
|
||||
export const SUPPORT_EMAIL =
|
||||
process.env.NEXT_PUBLIC_SUPPORT_EMAIL || "support@plane.so";
|
||||
export const SUPPORT_EMAIL = process.env.NEXT_PUBLIC_SUPPORT_EMAIL || "support@plane.so";
|
||||
// marketing links
|
||||
export const MARKETING_PRICING_PAGE_LINK = "https://plane.so/pricing";
|
||||
export const MARKETING_CONTACT_US_PAGE_LINK = "https://plane.so/contact";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// types
|
||||
// plane imports
|
||||
import { TEstimateSystems } from "@plane/types";
|
||||
|
||||
export const MAX_ESTIMATE_POINT_INPUT_LENGTH = 20;
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from "./ai";
|
||||
export * from "./analytics";
|
||||
export * from "./auth";
|
||||
export * from "./chart";
|
||||
export * from "./endpoints";
|
||||
@@ -22,7 +21,7 @@ export * from "./module";
|
||||
export * from "./project";
|
||||
export * from "./views";
|
||||
export * from "./themes";
|
||||
export * from "./inbox";
|
||||
export * from "./intake";
|
||||
export * from "./profile";
|
||||
export * from "./workspace-drafts";
|
||||
export * from "./label";
|
||||
@@ -34,4 +33,5 @@ export * from "./emoji";
|
||||
export * from "./subscription";
|
||||
export * from "./settings";
|
||||
export * from "./icon";
|
||||
export * from "./analytics-v2";
|
||||
export * from "./estimates";
|
||||
export * from "./analytics";
|
||||
|
||||
@@ -95,3 +95,32 @@ export const INBOX_ISSUE_SORT_BY_OPTIONS = [
|
||||
i18n_label: "common.sort.desc",
|
||||
},
|
||||
];
|
||||
|
||||
export enum EPastDurationFilters {
|
||||
TODAY = "today",
|
||||
YESTERDAY = "yesterday",
|
||||
LAST_7_DAYS = "last_7_days",
|
||||
LAST_30_DAYS = "last_30_days",
|
||||
}
|
||||
|
||||
export const PAST_DURATION_FILTER_OPTIONS: {
|
||||
name: string;
|
||||
value: string;
|
||||
}[] = [
|
||||
{
|
||||
name: "Today",
|
||||
value: EPastDurationFilters.TODAY,
|
||||
},
|
||||
{
|
||||
name: "Yesterday",
|
||||
value: EPastDurationFilters.YESTERDAY,
|
||||
},
|
||||
{
|
||||
name: "Last 7 days",
|
||||
value: EPastDurationFilters.LAST_7_DAYS,
|
||||
},
|
||||
{
|
||||
name: "Last 30 days",
|
||||
value: EPastDurationFilters.LAST_30_DAYS,
|
||||
},
|
||||
];
|
||||
@@ -136,45 +136,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_PAGE: TIssueFiltersToDisplayByPageType = {
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: [
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"state_detail.group",
|
||||
"priority",
|
||||
"labels",
|
||||
"assignees",
|
||||
"created_by",
|
||||
null,
|
||||
],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups"],
|
||||
},
|
||||
},
|
||||
},
|
||||
draft_issues: {
|
||||
list: {
|
||||
filters: ["priority", "state_group", "cycle", "module", "labels", "start_date", "target_date", "issue_type"],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "cycle", "module", "priority", "project", "labels", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups"],
|
||||
},
|
||||
},
|
||||
kanban: {
|
||||
filters: ["priority", "state_group", "cycle", "module", "labels", "start_date", "target_date", "issue_type"],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "cycle", "module", "priority", "project", "labels"],
|
||||
group_by: ["state", "cycle", "module", "priority", "labels", "assignees", "created_by", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
// types
|
||||
import {
|
||||
TModuleLayoutOptions,
|
||||
TModuleOrderByOptions,
|
||||
TModuleStatus,
|
||||
} from "@plane/types";
|
||||
import { TModuleLayoutOptions, TModuleOrderByOptions, TModuleStatus } from "@plane/types";
|
||||
|
||||
export const MODULE_STATUS_COLORS: {
|
||||
[key in TModuleStatus]: string;
|
||||
} = {
|
||||
backlog: "#a3a3a2",
|
||||
planned: "#3f76ff",
|
||||
paused: "#525252",
|
||||
completed: "#16a34a",
|
||||
cancelled: "#ef4444",
|
||||
"in-progress": "#f39e1f",
|
||||
};
|
||||
|
||||
export const MODULE_STATUS: {
|
||||
i18n_label: string;
|
||||
@@ -15,42 +22,42 @@ export const MODULE_STATUS: {
|
||||
{
|
||||
i18n_label: "project_modules.status.backlog",
|
||||
value: "backlog",
|
||||
color: "#a3a3a2",
|
||||
color: MODULE_STATUS_COLORS.backlog,
|
||||
textColor: "text-custom-text-400",
|
||||
bgColor: "bg-custom-background-80",
|
||||
},
|
||||
{
|
||||
i18n_label: "project_modules.status.planned",
|
||||
value: "planned",
|
||||
color: "#3f76ff",
|
||||
color: MODULE_STATUS_COLORS.planned,
|
||||
textColor: "text-blue-500",
|
||||
bgColor: "bg-indigo-50",
|
||||
},
|
||||
{
|
||||
i18n_label: "project_modules.status.in_progress",
|
||||
value: "in-progress",
|
||||
color: "#f39e1f",
|
||||
color: MODULE_STATUS_COLORS["in-progress"],
|
||||
textColor: "text-amber-500",
|
||||
bgColor: "bg-amber-50",
|
||||
},
|
||||
{
|
||||
i18n_label: "project_modules.status.paused",
|
||||
value: "paused",
|
||||
color: "#525252",
|
||||
color: MODULE_STATUS_COLORS.paused,
|
||||
textColor: "text-custom-text-300",
|
||||
bgColor: "bg-custom-background-90",
|
||||
},
|
||||
{
|
||||
i18n_label: "project_modules.status.completed",
|
||||
value: "completed",
|
||||
color: "#16a34a",
|
||||
color: MODULE_STATUS_COLORS.completed,
|
||||
textColor: "text-green-600",
|
||||
bgColor: "bg-green-100",
|
||||
},
|
||||
{
|
||||
i18n_label: "project_modules.status.cancelled",
|
||||
value: "cancelled",
|
||||
color: "#ef4444",
|
||||
color: MODULE_STATUS_COLORS.cancelled,
|
||||
textColor: "text-red-500",
|
||||
bgColor: "bg-red-50",
|
||||
},
|
||||
|
||||
@@ -72,23 +72,23 @@ export const PLANE_COMMUNITY_PRODUCTS: Record<string, IPaymentProduct> = {
|
||||
prices: [
|
||||
{
|
||||
id: `price_yearly_${EProductSubscriptionEnum.BUSINESS}`,
|
||||
unit_amount: 0,
|
||||
unit_amount: 15600,
|
||||
recurring: "year",
|
||||
currency: "usd",
|
||||
workspace_amount: 0,
|
||||
workspace_amount: 15600,
|
||||
product: EProductSubscriptionEnum.BUSINESS,
|
||||
},
|
||||
{
|
||||
id: `price_monthly_${EProductSubscriptionEnum.BUSINESS}`,
|
||||
unit_amount: 0,
|
||||
unit_amount: 1500,
|
||||
recurring: "month",
|
||||
currency: "usd",
|
||||
workspace_amount: 0,
|
||||
workspace_amount: 1500,
|
||||
product: EProductSubscriptionEnum.BUSINESS,
|
||||
},
|
||||
],
|
||||
payment_quantity: 1,
|
||||
is_active: false,
|
||||
is_active: true,
|
||||
},
|
||||
[EProductSubscriptionEnum.ENTERPRISE]: {
|
||||
id: EProductSubscriptionEnum.ENTERPRISE,
|
||||
@@ -141,8 +141,8 @@ export const SUBSCRIPTION_REDIRECTION_URLS: Record<EProductSubscriptionEnum, Rec
|
||||
year: "https://app.plane.so/upgrade/pro/self-hosted?plan=year",
|
||||
},
|
||||
[EProductSubscriptionEnum.BUSINESS]: {
|
||||
month: TALK_TO_SALES_URL,
|
||||
year: TALK_TO_SALES_URL,
|
||||
month: "https://app.plane.so/upgrade/business/self-hosted?plan=month",
|
||||
year: "https://app.plane.so/upgrade/business/self-hosted?plan=year",
|
||||
},
|
||||
[EProductSubscriptionEnum.ENTERPRISE]: {
|
||||
month: TALK_TO_SALES_URL,
|
||||
|
||||
@@ -149,3 +149,12 @@ export const DEFAULT_PROJECT_FORM_VALUES: Partial<IProject> = {
|
||||
network: 2,
|
||||
project_lead: null,
|
||||
};
|
||||
|
||||
export enum EProjectFeatureKey {
|
||||
WORK_ITEMS = "work_items",
|
||||
CYCLES = "cycles",
|
||||
MODULES = "modules",
|
||||
VIEWS = "views",
|
||||
PAGES = "pages",
|
||||
INTAKE = "intake",
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client"
|
||||
|
||||
export type TStateGroups = "backlog" | "unstarted" | "started" | "completed" | "cancelled";
|
||||
|
||||
export type TDraggableData = {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"@types/reflect-metadata": "^0.1.0",
|
||||
"@types/ws": "^8.5.10",
|
||||
"tsup": "8.4.0",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": ">=4.21.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/editor",
|
||||
"version": "0.26.0",
|
||||
"version": "0.26.1",
|
||||
"description": "Core Editor that powers Plane",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
@@ -82,7 +82,7 @@
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"postcss": "^8.4.38",
|
||||
"tsup": "8.4.0",
|
||||
"typescript": "5.3.3"
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"keywords": [
|
||||
"editor",
|
||||
|
||||
@@ -2,30 +2,31 @@ import { HocuspocusProvider } from "@hocuspocus/provider";
|
||||
import { AnyExtension } from "@tiptap/core";
|
||||
import { SlashCommands } from "@/extensions";
|
||||
// plane editor types
|
||||
import { TIssueEmbedConfig } from "@/plane-editor/types";
|
||||
import { TEmbedConfig } from "@/plane-editor/types";
|
||||
// types
|
||||
import { TExtensions, TUserDetails } from "@/types";
|
||||
import { TExtensions, TFileHandler, TUserDetails } from "@/types";
|
||||
|
||||
type Props = {
|
||||
disabledExtensions?: TExtensions[];
|
||||
issueEmbedConfig: TIssueEmbedConfig | undefined;
|
||||
provider: HocuspocusProvider;
|
||||
export type TDocumentEditorAdditionalExtensionsProps = {
|
||||
disabledExtensions: TExtensions[];
|
||||
embedConfig: TEmbedConfig | undefined;
|
||||
fileHandler: TFileHandler;
|
||||
provider?: HocuspocusProvider;
|
||||
userDetails: TUserDetails;
|
||||
};
|
||||
|
||||
type ExtensionConfig = {
|
||||
export type TDocumentEditorAdditionalExtensionsRegistry = {
|
||||
isEnabled: (disabledExtensions: TExtensions[]) => boolean;
|
||||
getExtension: (props: Props) => AnyExtension;
|
||||
getExtension: (props: TDocumentEditorAdditionalExtensionsProps) => AnyExtension;
|
||||
};
|
||||
|
||||
const extensionRegistry: ExtensionConfig[] = [
|
||||
const extensionRegistry: TDocumentEditorAdditionalExtensionsRegistry[] = [
|
||||
{
|
||||
isEnabled: (disabledExtensions) => !disabledExtensions.includes("slash-commands"),
|
||||
getExtension: () => SlashCommands({}),
|
||||
getExtension: ({ disabledExtensions }) => SlashCommands({ disabledExtensions }),
|
||||
},
|
||||
];
|
||||
|
||||
export const DocumentEditorAdditionalExtensions = (_props: Props) => {
|
||||
export const DocumentEditorAdditionalExtensions = (_props: TDocumentEditorAdditionalExtensionsProps) => {
|
||||
const { disabledExtensions = [] } = _props;
|
||||
|
||||
const documentExtensions = extensionRegistry
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { AnyExtension, Extensions } from "@tiptap/core";
|
||||
// extensions
|
||||
import { SlashCommands } from "@/extensions/slash-commands/root";
|
||||
// types
|
||||
import { TExtensions, TFileHandler } from "@/types";
|
||||
|
||||
export type TRichTextEditorAdditionalExtensionsProps = {
|
||||
disabledExtensions: TExtensions[];
|
||||
fileHandler: TFileHandler;
|
||||
};
|
||||
|
||||
/**
|
||||
* Registry entry configuration for extensions
|
||||
*/
|
||||
export type TRichTextEditorAdditionalExtensionsRegistry = {
|
||||
/** Determines if the extension should be enabled based on disabled extensions */
|
||||
isEnabled: (disabledExtensions: TExtensions[]) => boolean;
|
||||
/** Returns the extension instance(s) when enabled */
|
||||
getExtension: (props: TRichTextEditorAdditionalExtensionsProps) => AnyExtension | undefined;
|
||||
};
|
||||
|
||||
const extensionRegistry: TRichTextEditorAdditionalExtensionsRegistry[] = [
|
||||
{
|
||||
isEnabled: (disabledExtensions) => !disabledExtensions.includes("slash-commands"),
|
||||
getExtension: ({ disabledExtensions }) =>
|
||||
SlashCommands({
|
||||
disabledExtensions,
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export const RichTextEditorAdditionalExtensions = (props: TRichTextEditorAdditionalExtensionsProps) => {
|
||||
const { disabledExtensions } = props;
|
||||
|
||||
const extensions: Extensions = extensionRegistry
|
||||
.filter((config) => config.isEnabled(disabledExtensions))
|
||||
.map((config) => config.getExtension(props))
|
||||
.filter((extension): extension is AnyExtension => extension !== undefined);
|
||||
|
||||
return extensions;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { AnyExtension, Extensions } from "@tiptap/core";
|
||||
// types
|
||||
import { TExtensions, TReadOnlyFileHandler } from "@/types";
|
||||
|
||||
export type TRichTextReadOnlyEditorAdditionalExtensionsProps = {
|
||||
disabledExtensions: TExtensions[];
|
||||
fileHandler: TReadOnlyFileHandler;
|
||||
};
|
||||
|
||||
/**
|
||||
* Registry entry configuration for extensions
|
||||
*/
|
||||
export type TRichTextReadOnlyEditorAdditionalExtensionsRegistry = {
|
||||
/** Determines if the extension should be enabled based on disabled extensions */
|
||||
isEnabled: (disabledExtensions: TExtensions[]) => boolean;
|
||||
/** Returns the extension instance(s) when enabled */
|
||||
getExtension: (props: TRichTextReadOnlyEditorAdditionalExtensionsProps) => AnyExtension | undefined;
|
||||
};
|
||||
|
||||
const extensionRegistry: TRichTextReadOnlyEditorAdditionalExtensionsRegistry[] = [];
|
||||
|
||||
export const RichTextReadOnlyEditorAdditionalExtensions = (props: TRichTextReadOnlyEditorAdditionalExtensionsProps) => {
|
||||
const { disabledExtensions } = props;
|
||||
|
||||
const extensions: Extensions = extensionRegistry
|
||||
.filter((config) => config.isEnabled(disabledExtensions))
|
||||
.map((config) => config.getExtension(props))
|
||||
.filter((extension): extension is AnyExtension => extension !== undefined);
|
||||
|
||||
return extensions;
|
||||
};
|
||||
@@ -15,6 +15,7 @@ export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => {
|
||||
disabledExtensions,
|
||||
displayConfig = DEFAULT_DISPLAY_CONFIG,
|
||||
editorClassName = "",
|
||||
extensions,
|
||||
fileHandler,
|
||||
forwardedRef,
|
||||
id,
|
||||
@@ -25,6 +26,7 @@ export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => {
|
||||
const editor = useReadOnlyEditor({
|
||||
disabledExtensions,
|
||||
editorClassName,
|
||||
extensions,
|
||||
fileHandler,
|
||||
forwardedRef,
|
||||
initialValue,
|
||||
|
||||
@@ -3,12 +3,20 @@ import { forwardRef, useCallback } from "react";
|
||||
import { EditorWrapper } from "@/components/editors";
|
||||
import { EditorBubbleMenu } from "@/components/menus";
|
||||
// extensions
|
||||
import { SideMenuExtension, SlashCommands } from "@/extensions";
|
||||
import { SideMenuExtension } from "@/extensions";
|
||||
// plane editor imports
|
||||
import { RichTextEditorAdditionalExtensions } from "@/plane-editor/extensions/rich-text/extensions";
|
||||
// types
|
||||
import { EditorRefApi, IRichTextEditor } from "@/types";
|
||||
|
||||
const RichTextEditor = (props: IRichTextEditor) => {
|
||||
const { disabledExtensions, dragDropEnabled, bubbleMenuEnabled = true, extensions: externalExtensions = [] } = props;
|
||||
const {
|
||||
disabledExtensions,
|
||||
dragDropEnabled,
|
||||
fileHandler,
|
||||
bubbleMenuEnabled = true,
|
||||
extensions: externalExtensions = [],
|
||||
} = props;
|
||||
|
||||
const getExtensions = useCallback(() => {
|
||||
const extensions = [
|
||||
@@ -17,17 +25,14 @@ const RichTextEditor = (props: IRichTextEditor) => {
|
||||
aiEnabled: false,
|
||||
dragDropEnabled: !!dragDropEnabled,
|
||||
}),
|
||||
...RichTextEditorAdditionalExtensions({
|
||||
disabledExtensions,
|
||||
fileHandler,
|
||||
}),
|
||||
];
|
||||
if (!disabledExtensions?.includes("slash-commands")) {
|
||||
extensions.push(
|
||||
SlashCommands({
|
||||
disabledExtensions,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}, [dragDropEnabled, disabledExtensions, externalExtensions]);
|
||||
}, [dragDropEnabled, disabledExtensions, externalExtensions, fileHandler]);
|
||||
|
||||
return (
|
||||
<EditorWrapper {...props} extensions={getExtensions()}>
|
||||
|
||||
@@ -1,11 +1,33 @@
|
||||
import { forwardRef } from "react";
|
||||
import { forwardRef, useCallback } from "react";
|
||||
// plane editor extensions
|
||||
import { RichTextReadOnlyEditorAdditionalExtensions } from "@/plane-editor/extensions/rich-text/read-only-extensions";
|
||||
// types
|
||||
import { EditorReadOnlyRefApi, IRichTextReadOnlyEditor } from "@/types";
|
||||
// local imports
|
||||
import { ReadOnlyEditorWrapper } from "../read-only-editor-wrapper";
|
||||
|
||||
const RichTextReadOnlyEditorWithRef = forwardRef<EditorReadOnlyRefApi, IRichTextReadOnlyEditor>((props, ref) => (
|
||||
<ReadOnlyEditorWrapper {...props} forwardedRef={ref as React.MutableRefObject<EditorReadOnlyRefApi | null>} />
|
||||
));
|
||||
const RichTextReadOnlyEditorWithRef = forwardRef<EditorReadOnlyRefApi, IRichTextReadOnlyEditor>((props, ref) => {
|
||||
const { disabledExtensions, fileHandler } = props;
|
||||
|
||||
const getExtensions = useCallback(() => {
|
||||
const extensions = [
|
||||
...RichTextReadOnlyEditorAdditionalExtensions({
|
||||
disabledExtensions,
|
||||
fileHandler,
|
||||
}),
|
||||
];
|
||||
|
||||
return extensions;
|
||||
}, [disabledExtensions, fileHandler]);
|
||||
|
||||
return (
|
||||
<ReadOnlyEditorWrapper
|
||||
{...props}
|
||||
extensions={getExtensions()}
|
||||
forwardedRef={ref as React.MutableRefObject<EditorReadOnlyRefApi | null>}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
RichTextReadOnlyEditorWithRef.displayName = "RichReadOnlyEditorWithRef";
|
||||
|
||||
|
||||
@@ -170,8 +170,9 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
|
||||
CustomTextAlignExtension,
|
||||
CustomCalloutExtension,
|
||||
UtilityExtension({
|
||||
isEditable: editable,
|
||||
disabledExtensions,
|
||||
fileHandler,
|
||||
isEditable: editable,
|
||||
}),
|
||||
CustomColorExtension,
|
||||
...CoreEditorAdditionalExtensions({
|
||||
|
||||
@@ -127,8 +127,9 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => {
|
||||
CustomTextAlignExtension,
|
||||
CustomCalloutReadOnlyExtension,
|
||||
UtilityExtension({
|
||||
isEditable: false,
|
||||
disabledExtensions,
|
||||
fileHandler,
|
||||
isEditable: false,
|
||||
}),
|
||||
...CoreReadOnlyEditorAdditionalExtensions({
|
||||
disabledExtensions,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { DropHandlerPlugin } from "@/plugins/drop";
|
||||
import { FilePlugins } from "@/plugins/file/root";
|
||||
import { MarkdownClipboardPlugin } from "@/plugins/markdown-clipboard";
|
||||
// types
|
||||
import { TFileHandler, TReadOnlyFileHandler } from "@/types";
|
||||
import { TExtensions, TFileHandler, TReadOnlyFileHandler } from "@/types";
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
interface Commands {
|
||||
@@ -24,13 +24,14 @@ export interface UtilityExtensionStorage {
|
||||
}
|
||||
|
||||
type Props = {
|
||||
disabledExtensions: TExtensions[];
|
||||
fileHandler: TFileHandler | TReadOnlyFileHandler;
|
||||
isEditable: boolean;
|
||||
};
|
||||
|
||||
export const UtilityExtension = (props: Props) => {
|
||||
const { fileHandler, isEditable } = props;
|
||||
const { restore: restoreImageFn } = fileHandler;
|
||||
const { disabledExtensions, fileHandler, isEditable } = props;
|
||||
const { restore } = fileHandler;
|
||||
|
||||
return Extension.create<Record<string, unknown>, UtilityExtensionStorage>({
|
||||
name: "utility",
|
||||
@@ -45,12 +46,15 @@ export const UtilityExtension = (props: Props) => {
|
||||
}),
|
||||
...codemark({ markType: this.editor.schema.marks.code }),
|
||||
MarkdownClipboardPlugin(this.editor),
|
||||
DropHandlerPlugin(this.editor),
|
||||
DropHandlerPlugin({
|
||||
disabledExtensions,
|
||||
editor: this.editor,
|
||||
}),
|
||||
];
|
||||
},
|
||||
|
||||
onCreate() {
|
||||
restorePublicImages(this.editor, restoreImageFn);
|
||||
restorePublicImages(this.editor, restore);
|
||||
},
|
||||
|
||||
addStorage() {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// plane imports
|
||||
import { TDocumentPayload, TDuplicateAssetData, TDuplicateAssetResponse } from "@plane/types";
|
||||
import { TEditorAssetType } from "@plane/types/src/enums";
|
||||
// local imports
|
||||
import { convertHTMLDocumentToAllFormats } from "./yjs-utils";
|
||||
|
||||
/**
|
||||
* @description function to extract all image assets from HTML content
|
||||
* @param htmlContent
|
||||
* @returns {string[]} array of image asset sources
|
||||
*/
|
||||
export const extractImageAssetsFromHTMLContent = (htmlContent: string): string[] => {
|
||||
// create a DOM parser
|
||||
const parser = new DOMParser();
|
||||
// parse the HTML string into a DOM document
|
||||
const doc = parser.parseFromString(htmlContent, "text/html");
|
||||
// get all image components
|
||||
const imageComponents = doc.querySelectorAll("image-component");
|
||||
// collect all unique image sources
|
||||
const imageSources = new Set<string>();
|
||||
// extract sources from image components
|
||||
imageComponents.forEach((component) => {
|
||||
const src = component.getAttribute("src");
|
||||
if (src) imageSources.add(src);
|
||||
});
|
||||
return Array.from(imageSources);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description function to replace image assets in HTML content with new IDs
|
||||
* @param props
|
||||
* @returns {string} HTML content with replaced image assets
|
||||
*/
|
||||
export const replaceImageAssetsInHTMLContent = (props: {
|
||||
htmlContent: string;
|
||||
assetMap: Record<string, string>;
|
||||
}): string => {
|
||||
const { htmlContent, assetMap } = props;
|
||||
// create a DOM parser
|
||||
const parser = new DOMParser();
|
||||
// parse the HTML string into a DOM document
|
||||
const doc = parser.parseFromString(htmlContent, "text/html");
|
||||
// replace sources in image components
|
||||
const imageComponents = doc.querySelectorAll("image-component");
|
||||
imageComponents.forEach((component) => {
|
||||
const oldSrc = component.getAttribute("src");
|
||||
if (oldSrc && assetMap[oldSrc]) {
|
||||
component.setAttribute("src", assetMap[oldSrc]);
|
||||
}
|
||||
});
|
||||
// serialize the document back into a string
|
||||
return doc.body.innerHTML;
|
||||
};
|
||||
|
||||
export const getEditorContentWithReplacedImageAssets = async (props: {
|
||||
descriptionHTML: string;
|
||||
entityId: string;
|
||||
entityType: TEditorAssetType;
|
||||
projectId: string | undefined;
|
||||
variant: "rich" | "document";
|
||||
duplicateAssetService: (params: TDuplicateAssetData) => Promise<TDuplicateAssetResponse>;
|
||||
}): Promise<TDocumentPayload> => {
|
||||
const { descriptionHTML, entityId, entityType, projectId, variant, duplicateAssetService } = props;
|
||||
let replacedDescription = descriptionHTML;
|
||||
// step 1: extract image assets from the description
|
||||
const imageAssets = extractImageAssetsFromHTMLContent(descriptionHTML);
|
||||
if (imageAssets.length !== 0) {
|
||||
// step 2: duplicate the image assets
|
||||
const duplicateAssetsResponse = await duplicateAssetService({
|
||||
entity_id: entityId,
|
||||
entity_type: entityType,
|
||||
project_id: projectId,
|
||||
asset_ids: imageAssets,
|
||||
});
|
||||
if (Object.keys(duplicateAssetsResponse ?? {}).length > 0) {
|
||||
// step 3: replace the image assets in the description
|
||||
replacedDescription = replaceImageAssetsInHTMLContent({
|
||||
htmlContent: descriptionHTML,
|
||||
assetMap: duplicateAssetsResponse,
|
||||
});
|
||||
}
|
||||
}
|
||||
// step 4: convert the description to the document payload
|
||||
const documentPayload = convertHTMLDocumentToAllFormats({
|
||||
document_html: replacedDescription,
|
||||
variant,
|
||||
});
|
||||
return documentPayload;
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import { generateHTML, generateJSON } from "@tiptap/html";
|
||||
import { prosemirrorJSONToYDoc, yXmlFragmentToProseMirrorRootNode } from "y-prosemirror";
|
||||
import * as Y from "yjs";
|
||||
// extensions
|
||||
import { TDocumentPayload } from "@plane/types";
|
||||
import {
|
||||
CoreEditorExtensionsWithoutProps,
|
||||
DocumentEditorExtensionsWithoutProps,
|
||||
@@ -140,3 +141,50 @@ export const getAllDocumentFormatsFromDocumentEditorBinaryData = (
|
||||
contentHTML,
|
||||
};
|
||||
};
|
||||
|
||||
type TConvertHTMLDocumentToAllFormatsArgs = {
|
||||
document_html: string;
|
||||
variant: "rich" | "document";
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Converts HTML content to all supported document formats (JSON, HTML, and binary)
|
||||
* @param {TConvertHTMLDocumentToAllFormatsArgs} args - Arguments containing HTML content and variant type
|
||||
* @param {string} args.document_html - The HTML content to convert
|
||||
* @param {"rich" | "document"} args.variant - The type of editor variant to use for conversion
|
||||
* @returns {TDocumentPayload} Object containing the document in all supported formats
|
||||
* @throws {Error} If an invalid variant is provided
|
||||
*/
|
||||
export const convertHTMLDocumentToAllFormats = (args: TConvertHTMLDocumentToAllFormatsArgs): TDocumentPayload => {
|
||||
const { document_html, variant } = args;
|
||||
|
||||
let allFormats: TDocumentPayload;
|
||||
|
||||
if (variant === "rich") {
|
||||
// Convert HTML to binary format for rich text editor
|
||||
const contentBinary = getBinaryDataFromRichTextEditorHTMLString(document_html);
|
||||
// Generate all document formats from the binary data
|
||||
const { contentBinaryEncoded, contentHTML, contentJSON } =
|
||||
getAllDocumentFormatsFromRichTextEditorBinaryData(contentBinary);
|
||||
allFormats = {
|
||||
description: contentJSON,
|
||||
description_html: contentHTML,
|
||||
description_binary: contentBinaryEncoded,
|
||||
};
|
||||
} else if (variant === "document") {
|
||||
// Convert HTML to binary format for document editor
|
||||
const contentBinary = getBinaryDataFromDocumentEditorHTMLString(document_html);
|
||||
// Generate all document formats from the binary data
|
||||
const { contentBinaryEncoded, contentHTML, contentJSON } =
|
||||
getAllDocumentFormatsFromDocumentEditorBinaryData(contentBinary);
|
||||
allFormats = {
|
||||
description: contentJSON,
|
||||
description_html: contentHTML,
|
||||
description_binary: contentBinaryEncoded,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Invalid variant provided: ${variant}`);
|
||||
}
|
||||
|
||||
return allFormats;
|
||||
};
|
||||
|
||||
@@ -92,7 +92,8 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
|
||||
...(extensions ?? []),
|
||||
...DocumentEditorAdditionalExtensions({
|
||||
disabledExtensions,
|
||||
issueEmbedConfig: embedHandler?.issue,
|
||||
embedConfig: embedHandler,
|
||||
fileHandler,
|
||||
provider,
|
||||
userDetails: user,
|
||||
}),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Fragment, Slice, Node, Schema } from "@tiptap/pm/model";
|
||||
import { NodeSelection } from "@tiptap/pm/state";
|
||||
// @ts-expect-error __serializeForClipboard's is not exported
|
||||
import { __serializeForClipboard, EditorView } from "@tiptap/pm/view";
|
||||
import { EditorView } from "@tiptap/pm/view";
|
||||
// constants
|
||||
import { CORE_EXTENSIONS } from "@/constants/extension";
|
||||
// extensions
|
||||
@@ -417,7 +416,7 @@ const handleNodeSelection = (
|
||||
}
|
||||
|
||||
const slice = view.state.selection.content();
|
||||
const { dom, text } = __serializeForClipboard(view, slice);
|
||||
const { dom, text } = view.serializeForClipboard(slice);
|
||||
|
||||
if (event instanceof DragEvent && event.dataTransfer) {
|
||||
event.dataTransfer.clearData();
|
||||
|
||||
@@ -3,10 +3,17 @@ import { Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
// constants
|
||||
import { ACCEPTED_ATTACHMENT_MIME_TYPES, ACCEPTED_IMAGE_MIME_TYPES } from "@/constants/config";
|
||||
// types
|
||||
import { TEditorCommands } from "@/types";
|
||||
import { TEditorCommands, TExtensions } from "@/types";
|
||||
|
||||
export const DropHandlerPlugin = (editor: Editor): Plugin =>
|
||||
new Plugin({
|
||||
type Props = {
|
||||
disabledExtensions?: TExtensions[];
|
||||
editor: Editor;
|
||||
};
|
||||
|
||||
export const DropHandlerPlugin = (props: Props): Plugin => {
|
||||
const { disabledExtensions, editor } = props;
|
||||
|
||||
return new Plugin({
|
||||
key: new PluginKey("drop-handler-plugin"),
|
||||
props: {
|
||||
handlePaste: (view, event) => {
|
||||
@@ -25,6 +32,7 @@ export const DropHandlerPlugin = (editor: Editor): Plugin =>
|
||||
if (acceptedFiles.length) {
|
||||
const pos = view.state.selection.from;
|
||||
insertFilesSafely({
|
||||
disabledExtensions,
|
||||
editor,
|
||||
files: acceptedFiles,
|
||||
initialPos: pos,
|
||||
@@ -58,6 +66,7 @@ export const DropHandlerPlugin = (editor: Editor): Plugin =>
|
||||
if (coordinates) {
|
||||
const pos = coordinates.pos;
|
||||
insertFilesSafely({
|
||||
disabledExtensions,
|
||||
editor,
|
||||
files: acceptedFiles,
|
||||
initialPos: pos,
|
||||
@@ -71,8 +80,10 @@ export const DropHandlerPlugin = (editor: Editor): Plugin =>
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
type InsertFilesSafelyArgs = {
|
||||
disabledExtensions?: TExtensions[];
|
||||
editor: Editor;
|
||||
event: "insert" | "drop";
|
||||
files: File[];
|
||||
@@ -81,7 +92,7 @@ type InsertFilesSafelyArgs = {
|
||||
};
|
||||
|
||||
export const insertFilesSafely = async (args: InsertFilesSafelyArgs) => {
|
||||
const { editor, event, files, initialPos, type } = args;
|
||||
const { disabledExtensions, editor, event, files, initialPos, type } = args;
|
||||
let pos = initialPos;
|
||||
|
||||
for (const file of files) {
|
||||
@@ -100,7 +111,7 @@ export const insertFilesSafely = async (args: InsertFilesSafelyArgs) => {
|
||||
else if (ACCEPTED_ATTACHMENT_MIME_TYPES.includes(file.type)) fileType = "attachment";
|
||||
}
|
||||
// insert file depending on the type at the current position
|
||||
if (fileType === "image") {
|
||||
if (fileType === "image" && !disabledExtensions?.includes("image")) {
|
||||
editor.commands.insertImageComponent({
|
||||
file,
|
||||
pos,
|
||||
|
||||
@@ -160,6 +160,7 @@ export interface IReadOnlyEditorProps {
|
||||
disabledExtensions: TExtensions[];
|
||||
displayConfig?: TDisplayConfig;
|
||||
editorClassName?: string;
|
||||
extensions?: Extensions;
|
||||
fileHandler: TReadOnlyFileHandler;
|
||||
forwardedRef?: React.MutableRefObject<EditorReadOnlyRefApi | null>;
|
||||
id: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@plane/eslint-config",
|
||||
"private": true,
|
||||
"version": "0.26.0",
|
||||
"version": "0.26.1",
|
||||
"license": "AGPL-3.0",
|
||||
"files": [
|
||||
"library.js",
|
||||
@@ -18,6 +18,6 @@
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"typescript": "5.3.3"
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/hooks",
|
||||
"version": "0.26.0",
|
||||
"version": "0.26.1",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "React hooks that are shared across multiple apps internally",
|
||||
"private": true,
|
||||
@@ -23,6 +23,6 @@
|
||||
"@types/node": "^22.5.4",
|
||||
"@types/react": "^18.3.11",
|
||||
"tsup": "8.4.0",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/i18n",
|
||||
"version": "0.26.0",
|
||||
"version": "0.26.1",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "I18n shared across multiple apps internally",
|
||||
"private": true,
|
||||
@@ -17,6 +17,6 @@
|
||||
"devDependencies": {
|
||||
"@plane/eslint-config": "*",
|
||||
"@types/node": "^22.5.4",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export const SUPPORTED_LANGUAGES: ILanguageOption[] = [
|
||||
export enum ETranslationFiles {
|
||||
TRANSLATIONS = "translations",
|
||||
ACCESSIBILITY = "accessibility",
|
||||
EDITOR = "editor",
|
||||
}
|
||||
|
||||
export const LANGUAGE_STORAGE_KEY = "userLanguage";
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Nepodařilo se odstranit projekt z oblíbených. Zkuste to prosím znovu.",
|
||||
"project_created_successfully": "Projekt úspěšně vytvořen",
|
||||
"project_created_successfully_description": "Projekt byl úspěšně vytvořen. Nyní můžete začít přidávat pracovní položky.",
|
||||
"project_name_already_taken": "Název projektu už je zabraný.",
|
||||
"project_identifier_already_taken": "Identifikátor projektu už je zabraný.",
|
||||
"project_cover_image_alt": "Úvodní obrázek projektu",
|
||||
"name_is_required": "Název je povinný",
|
||||
"title_should_be_less_than_255_characters": "Název by měl být kratší než 255 znaků",
|
||||
@@ -866,7 +868,21 @@
|
||||
"view": "Pohled",
|
||||
"deactivated_user": "Deaktivovaný uživatel",
|
||||
"apply": "Použít",
|
||||
"applying": "Používání"
|
||||
"applying": "Používání",
|
||||
"users": "Uživatelé",
|
||||
"admins": "Administrátoři",
|
||||
"guests": "Hosté",
|
||||
"on_track": "Na správné cestě",
|
||||
"off_track": "Mimo plán",
|
||||
"at_risk": "V ohrožení",
|
||||
"timeline": "Časová osa",
|
||||
"completion": "Dokončení",
|
||||
"upcoming": "Nadcházející",
|
||||
"completed": "Dokončeno",
|
||||
"in_progress": "Probíhá",
|
||||
"planned": "Plánováno",
|
||||
"paused": "Pozastaveno",
|
||||
"no_of": "Počet {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "Osa X",
|
||||
@@ -1316,19 +1332,6 @@
|
||||
"custom": "Vlastní analytika"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Sledujte pokrok, vytížení a alokace. Identifikujte trendy, odstraňte překážky a zrychlete práci",
|
||||
"description": "Sledujte rozsah vs. poptávku, odhady a rozsah. Zjistěte výkonnost členů a týmů, zajistěte včasné dokončení projektů.",
|
||||
"primary_button": {
|
||||
"text": "Začněte první projekt",
|
||||
"comic": {
|
||||
"title": "Analytika funguje nejlépe s Cykly + Moduly",
|
||||
"description": "Nejprve časově ohraničte práci do Cyklů a seskupte položky přesahující cyklus do Modulů. Najdete je v levém menu."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Pracovní položky přiřazené vám, rozdělené podle stavu, se zde zobrazí.",
|
||||
"title": "Zatím žádná data"
|
||||
@@ -1344,21 +1347,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Vytvořeno vs Vyřešeno",
|
||||
"customized_insights": "Přizpůsobené přehledy",
|
||||
"backlog_work_items": "Pracovní položky v backlogu",
|
||||
"backlog_work_items": "Backlog {entity}",
|
||||
"active_projects": "Aktivní projekty",
|
||||
"trend_on_charts": "Trend na grafech",
|
||||
"all_projects": "Všechny projekty",
|
||||
"summary_of_projects": "Souhrn projektů",
|
||||
"project_insights": "Přehled projektu",
|
||||
"started_work_items": "Zahájené pracovní položky",
|
||||
"total_work_items": "Celkový počet pracovních položek",
|
||||
"started_work_items": "Zahájené {entity}",
|
||||
"total_work_items": "Celkový počet {entity}",
|
||||
"total_projects": "Celkový počet projektů",
|
||||
"total_admins": "Celkový počet administrátorů",
|
||||
"total_users": "Celkový počet uživatelů",
|
||||
"total_intake": "Celkový příjem",
|
||||
"un_started_work_items": "Nezahájené pracovní položky",
|
||||
"un_started_work_items": "Nezahájené {entity}",
|
||||
"total_guests": "Celkový počet hostů",
|
||||
"completed_work_items": "Dokončené pracovní položky"
|
||||
"completed_work_items": "Dokončené {entity}",
|
||||
"total": "Celkový počet {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Projekt} few {Projekty} other {Projektů}}",
|
||||
@@ -2462,5 +2466,9 @@
|
||||
"last_edited_by": "Naposledy upraveno uživatelem",
|
||||
"previously_edited_by": "Dříve upraveno uživatelem",
|
||||
"edited_by": "Upraveno uživatelem"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane se nespustil. To může být způsobeno tím, že se jeden nebo více služeb Plane nepodařilo spustit.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Vyberte View Logs z setup.sh a Docker logů, abyste si byli jisti."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Projekt konnte nicht aus den Favoriten entfernt werden. Bitte versuchen Sie es erneut.",
|
||||
"project_created_successfully": "Projekt erfolgreich erstellt",
|
||||
"project_created_successfully_description": "Das Projekt wurde erfolgreich erstellt. Sie können nun Arbeitselemente hinzufügen.",
|
||||
"project_name_already_taken": "Der Projektname ist bereits vergeben.",
|
||||
"project_identifier_already_taken": "Der Projekt-Identifier ist bereits vergeben.",
|
||||
"project_cover_image_alt": "Titelbild des Projekts",
|
||||
"name_is_required": "Name ist erforderlich",
|
||||
"title_should_be_less_than_255_characters": "Der Titel sollte weniger als 255 Zeichen enthalten",
|
||||
@@ -866,7 +868,21 @@
|
||||
"view": "Ansicht",
|
||||
"deactivated_user": "Deaktivierter Benutzer",
|
||||
"apply": "Anwenden",
|
||||
"applying": "Wird angewendet"
|
||||
"applying": "Wird angewendet",
|
||||
"users": "Benutzer",
|
||||
"admins": "Administratoren",
|
||||
"guests": "Gäste",
|
||||
"on_track": "Im Plan",
|
||||
"off_track": "Außer Plan",
|
||||
"at_risk": "Gefährdet",
|
||||
"timeline": "Zeitleiste",
|
||||
"completion": "Fertigstellung",
|
||||
"upcoming": "Bevorstehend",
|
||||
"completed": "Abgeschlossen",
|
||||
"in_progress": "In Bearbeitung",
|
||||
"planned": "Geplant",
|
||||
"paused": "Pausiert",
|
||||
"no_of": "Anzahl {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "X-Achse",
|
||||
@@ -1316,19 +1332,6 @@
|
||||
"custom": "Benutzerdefinierte Analysen"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Verfolgen Sie Fortschritt, Auslastung und Zuordnungen. Erkennen Sie Trends, entfernen Sie Blocker und beschleunigen Sie die Arbeit",
|
||||
"description": "Behalten Sie Umfang vs. Nachfrage, Schätzungen und Umfang im Blick. Verfolgen Sie die Leistung von Mitgliedern und Teams, um sicherzustellen, dass Projekte pünktlich abgeschlossen werden.",
|
||||
"primary_button": {
|
||||
"text": "Erstes Projekt starten",
|
||||
"comic": {
|
||||
"title": "Analysen funktionieren am besten mit Zyklen + Modulen",
|
||||
"description": "Begrenzen Sie zuerst Arbeit zeitlich in Zyklen und gruppieren Sie die übergreifenden Elemente in Module. Sie finden sie im linken Menü."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Ihnen zugewiesene Arbeitselemente, aufgeschlüsselt nach Status, werden hier angezeigt.",
|
||||
"title": "Noch keine Daten"
|
||||
@@ -1344,21 +1347,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Erstellt vs Gelöst",
|
||||
"customized_insights": "Individuelle Einblicke",
|
||||
"backlog_work_items": "Backlog-Arbeitselemente",
|
||||
"backlog_work_items": "Backlog-{entity}",
|
||||
"active_projects": "Aktive Projekte",
|
||||
"trend_on_charts": "Trend in Diagrammen",
|
||||
"all_projects": "Alle Projekte",
|
||||
"summary_of_projects": "Projektübersicht",
|
||||
"project_insights": "Projekteinblicke",
|
||||
"started_work_items": "Begonnene Arbeitselemente",
|
||||
"total_work_items": "Gesamte Arbeitselemente",
|
||||
"started_work_items": "Begonnene {entity}",
|
||||
"total_work_items": "Gesamte {entity}",
|
||||
"total_projects": "Gesamtprojekte",
|
||||
"total_admins": "Gesamtanzahl der Admins",
|
||||
"total_users": "Gesamtanzahl der Benutzer",
|
||||
"total_intake": "Gesamteinnahmen",
|
||||
"un_started_work_items": "Nicht begonnene Arbeitselemente",
|
||||
"un_started_work_items": "Nicht begonnene {entity}",
|
||||
"total_guests": "Gesamtanzahl der Gäste",
|
||||
"completed_work_items": "Abgeschlossene Arbeitselemente"
|
||||
"completed_work_items": "Abgeschlossene {entity}",
|
||||
"total": "Gesamte {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Projekt} few {Projekte} other {Projekte}}",
|
||||
@@ -2461,5 +2465,9 @@
|
||||
"last_edited_by": "Zuletzt bearbeitet von",
|
||||
"previously_edited_by": "Zuvor bearbeitet von",
|
||||
"edited_by": "Bearbeitet von"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane ist nicht gestartet. Dies könnte daran liegen, dass einer oder mehrere Plane-Services nicht starten konnten.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Wählen Sie View Logs aus setup.sh und Docker-Logs, um sicherzugehen."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -153,6 +153,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Couldn't remove the project from favorites. Please try again.",
|
||||
"project_created_successfully": "Project created successfully",
|
||||
"project_created_successfully_description": "Project created successfully. You can now start adding work items to it.",
|
||||
"project_name_already_taken": "The project name is already taken.",
|
||||
"project_identifier_already_taken": "The project identifier is already taken.",
|
||||
"project_cover_image_alt": "Project cover image",
|
||||
"name_is_required": "Name is required",
|
||||
"title_should_be_less_than_255_characters": "Title should be less than 255 characters",
|
||||
@@ -476,6 +478,9 @@
|
||||
"modules": "Modules",
|
||||
"labels": "Labels",
|
||||
"label": "Label",
|
||||
"admins": "Admins",
|
||||
"users": "Users",
|
||||
"guests": "Guests",
|
||||
"assignees": "Assignees",
|
||||
"assignee": "Assignee",
|
||||
"created_by": "Created by",
|
||||
@@ -612,6 +617,16 @@
|
||||
"quarter": "Quarter",
|
||||
"press_for_commands": "Press '/' for commands",
|
||||
"click_to_add_description": "Click to add description",
|
||||
"on_track": "On-Track",
|
||||
"off_track": "Off-Track",
|
||||
"at_risk": "At risk",
|
||||
"timeline": "Timeline",
|
||||
"completion": "Completion",
|
||||
"upcoming": "Upcoming",
|
||||
"completed": "Completed",
|
||||
"in_progress": "In progress",
|
||||
"planned": "Planned",
|
||||
"paused": "Paused",
|
||||
"search": {
|
||||
"label": "Search",
|
||||
"placeholder": "Type to search",
|
||||
@@ -709,7 +724,8 @@
|
||||
"deactivated_user": "Deactivated user",
|
||||
"apply": "Apply",
|
||||
"applying": "Applying",
|
||||
"overview": "Overview"
|
||||
"overview": "Overview",
|
||||
"no_of": "No. of {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "X-axis",
|
||||
@@ -1158,29 +1174,11 @@
|
||||
"scope_and_demand": "Scope and Demand",
|
||||
"custom": "Custom Analytics"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Track progress, workloads, and allocations. Spot trends, remove blockers, and move work faster",
|
||||
"description": "See scope versus demand, estimates, and scope creep. Get performance by team members and teams, and make sure your project runs on time.",
|
||||
"primary_button": {
|
||||
"text": "Start your first project",
|
||||
"comic": {
|
||||
"title": "Analytics works best with Cycles + Modules",
|
||||
"description": "First, timebox your work items into Cycles and, if you can, group work items that span more than a cycle into Modules. Check out both on the left nav."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"total_work_items": "Total work items",
|
||||
"started_work_items": "Started work items",
|
||||
"backlog_work_items": "Backlog work items",
|
||||
"un_started_work_items": "Unstarted work items",
|
||||
"completed_work_items": "Completed work items",
|
||||
"total_guests": "Total Guests",
|
||||
"total_intake": "Total Intake",
|
||||
"total_users": "Total Users",
|
||||
"total_admins": "Total Admins",
|
||||
"total_projects": "Total Projects",
|
||||
"total": "Total {entity}",
|
||||
"started_work_items": "Started {entity}",
|
||||
"backlog_work_items": "Backlog {entity}",
|
||||
"un_started_work_items": "Unstarted {entity}",
|
||||
"completed_work_items": "Completed {entity}",
|
||||
"project_insights": "Project Insights",
|
||||
"summary_of_projects": "Summary of Projects",
|
||||
"all_projects": "All Projects",
|
||||
@@ -1188,7 +1186,7 @@
|
||||
"active_projects": "Active Projects",
|
||||
"customized_insights": "Customized Insights",
|
||||
"created_vs_resolved": "Created vs Resolved",
|
||||
"empty_state_v2": {
|
||||
"empty_state": {
|
||||
"project_insights": {
|
||||
"title": "No data yet",
|
||||
"description": "Work items assigned to you, broken down by state, will show up here."
|
||||
@@ -1312,23 +1310,23 @@
|
||||
}
|
||||
},
|
||||
"account_settings": {
|
||||
"profile":{},
|
||||
"preferences":{
|
||||
"profile": {},
|
||||
"preferences": {
|
||||
"heading": "Preferences",
|
||||
"description": "Customize your app experience the way you work"
|
||||
},
|
||||
"notifications":{
|
||||
"notifications": {
|
||||
"heading": "Email notifications",
|
||||
"description": "Stay in the loop on Work items you are subscribed to. Enable this to get notified."
|
||||
"description": "Stay in the loop on Work items you are subscribed to. Enable this to get notified."
|
||||
},
|
||||
"security":{
|
||||
"security": {
|
||||
"heading": "Security"
|
||||
},
|
||||
"api_tokens":{
|
||||
"api_tokens": {
|
||||
"heading": "Personal Access Tokens",
|
||||
"description": "Generate secure API tokens to integrate your data with external systems and applications."
|
||||
},
|
||||
"activity":{
|
||||
"activity": {
|
||||
"heading": "Activity",
|
||||
"description": "Track your recent actions and changes across all projects and work items."
|
||||
}
|
||||
@@ -1400,7 +1398,7 @@
|
||||
},
|
||||
"billing_and_plans": {
|
||||
"heading": "Billing & Plans",
|
||||
"description":"Choose your plan, manage subscriptions, and easily upgrade as your needs grow.",
|
||||
"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",
|
||||
@@ -2344,5 +2342,9 @@
|
||||
"last_edited_by": "Last edited by",
|
||||
"previously_edited_by": "Previously edited by",
|
||||
"edited_by": "Edited by"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane didn't start up. This could be because one or more Plane services failed to start.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Choose View Logs from setup.sh and Docker logs to be sure."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -318,6 +318,8 @@
|
||||
"failed_to_remove_project_from_favorites": "No se pudo eliminar el proyecto de favoritos. Por favor, inténtalo de nuevo.",
|
||||
"project_created_successfully": "Proyecto creado exitosamente",
|
||||
"project_created_successfully_description": "Proyecto creado exitosamente. Ahora puedes comenzar a agregar elementos de trabajo.",
|
||||
"project_name_already_taken": "El nombre del proyecto ya está en uso.",
|
||||
"project_identifier_already_taken": "El identificador del proyecto ya está en uso.",
|
||||
"project_cover_image_alt": "Imagen de portada del proyecto",
|
||||
"name_is_required": "El nombre es requerido",
|
||||
"title_should_be_less_than_255_characters": "El título debe tener menos de 255 caracteres",
|
||||
@@ -869,7 +871,21 @@
|
||||
"view": "Ver",
|
||||
"deactivated_user": "Usuario desactivado",
|
||||
"apply": "Aplicar",
|
||||
"applying": "Aplicando"
|
||||
"applying": "Aplicando",
|
||||
"users": "Usuarios",
|
||||
"admins": "Administradores",
|
||||
"guests": "Invitados",
|
||||
"on_track": "En camino",
|
||||
"off_track": "Fuera de camino",
|
||||
"at_risk": "En riesgo",
|
||||
"timeline": "Cronograma",
|
||||
"completion": "Finalización",
|
||||
"upcoming": "Próximo",
|
||||
"completed": "Completado",
|
||||
"in_progress": "En progreso",
|
||||
"planned": "Planificado",
|
||||
"paused": "Pausado",
|
||||
"no_of": "N.º de {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "Eje X",
|
||||
@@ -1319,19 +1335,6 @@
|
||||
"custom": "Análisis Personalizado"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Rastrea el progreso, cargas de trabajo y asignaciones. Identifica tendencias, elimina bloqueos y mueve el trabajo más rápido",
|
||||
"description": "Observa el alcance versus la demanda, estimaciones y el aumento del alcance. Obtén el rendimiento por miembros del equipo y equipos, y asegúrate de que tu proyecto se ejecute a tiempo.",
|
||||
"primary_button": {
|
||||
"text": "Inicia tu primer proyecto",
|
||||
"comic": {
|
||||
"title": "El análisis funciona mejor con Ciclos + Módulos",
|
||||
"description": "Primero, organiza tus elementos de trabajo en Ciclos y, si puedes, agrupa los elementos de trabajo que abarcan más de un ciclo en Módulos. Revisa ambos en la navegación izquierda."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Los elementos de trabajo asignados a ti, desglosados por estado, aparecerán aquí.",
|
||||
"title": "Aún no hay datos"
|
||||
@@ -1347,21 +1350,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Creado vs Resuelto",
|
||||
"customized_insights": "Información personalizada",
|
||||
"backlog_work_items": "Elementos de trabajo en backlog",
|
||||
"backlog_work_items": "{entity} en backlog",
|
||||
"active_projects": "Proyectos activos",
|
||||
"trend_on_charts": "Tendencia en gráficos",
|
||||
"all_projects": "Todos los proyectos",
|
||||
"summary_of_projects": "Resumen de proyectos",
|
||||
"project_insights": "Información del proyecto",
|
||||
"started_work_items": "Elementos de trabajo iniciados",
|
||||
"total_work_items": "Total de elementos de trabajo",
|
||||
"started_work_items": "{entity} iniciados",
|
||||
"total_work_items": "Total de {entity}",
|
||||
"total_projects": "Total de proyectos",
|
||||
"total_admins": "Total de administradores",
|
||||
"total_users": "Total de usuarios",
|
||||
"total_intake": "Ingreso total",
|
||||
"un_started_work_items": "Elementos de trabajo no iniciados",
|
||||
"un_started_work_items": "{entity} no iniciados",
|
||||
"total_guests": "Total de invitados",
|
||||
"completed_work_items": "Elementos de trabajo completados"
|
||||
"completed_work_items": "{entity} completados",
|
||||
"total": "Total de {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Proyecto} other {Proyectos}}",
|
||||
@@ -2464,5 +2468,9 @@
|
||||
"last_edited_by": "Última edición por",
|
||||
"previously_edited_by": "Editado anteriormente por",
|
||||
"edited_by": "Editado por"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane no se inició. Esto podría deberse a que uno o más servicios de Plane fallaron al iniciar.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Selecciona View Logs desde setup.sh y los logs de Docker para estar seguro."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Impossible de supprimer le projet des favoris. Veuillez réessayer.",
|
||||
"project_created_successfully": "Projet créé avec succès",
|
||||
"project_created_successfully_description": "Projet créé avec succès. Vous pouvez maintenant commencer à ajouter des éléments de travail.",
|
||||
"project_name_already_taken": "Le nom du projet est déjà pris.",
|
||||
"project_identifier_already_taken": "L’identifiant du projet est déjà pris.",
|
||||
"project_cover_image_alt": "Image de couverture du projet",
|
||||
"name_is_required": "Le nom est requis",
|
||||
"title_should_be_less_than_255_characters": "Le titre doit faire moins de 255 caractères",
|
||||
@@ -867,7 +869,21 @@
|
||||
"view": "Afficher",
|
||||
"deactivated_user": "Utilisateur désactivé",
|
||||
"apply": "Appliquer",
|
||||
"applying": "Application"
|
||||
"applying": "Application",
|
||||
"users": "Utilisateurs",
|
||||
"admins": "Administrateurs",
|
||||
"guests": "Invités",
|
||||
"on_track": "Sur la bonne voie",
|
||||
"off_track": "Hors de la bonne voie",
|
||||
"at_risk": "À risque",
|
||||
"timeline": "Chronologie",
|
||||
"completion": "Achèvement",
|
||||
"upcoming": "À venir",
|
||||
"completed": "Terminé",
|
||||
"in_progress": "En cours",
|
||||
"planned": "Planifié",
|
||||
"paused": "En pause",
|
||||
"no_of": "Nº de {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "Axe X",
|
||||
@@ -1317,19 +1333,6 @@
|
||||
"custom": "Analytique Personnalisée"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Suivez les progrès, les charges de travail et les allocations. Repérez les tendances, supprimez les blocages et accélérez le travail",
|
||||
"description": "Visualisez la portée par rapport à la demande, les estimations et l'augmentation de la portée. Obtenez les performances par membres de l'équipe et équipes, et assurez-vous que votre projet se déroule dans les délais.",
|
||||
"primary_button": {
|
||||
"text": "Commencez votre premier projet",
|
||||
"comic": {
|
||||
"title": "L'analytique fonctionne mieux avec les Cycles + Modules",
|
||||
"description": "D'abord, planifiez vos éléments de travail dans des Cycles et, si possible, regroupez les éléments de travail qui s'étendent sur plus d'un cycle dans des Modules. Consultez les deux dans la navigation de gauche."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Les éléments de travail qui vous sont assignés, répartis par état, s'afficheront ici.",
|
||||
"title": "Pas encore de données"
|
||||
@@ -1345,21 +1348,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Créé vs Résolu",
|
||||
"customized_insights": "Informations personnalisées",
|
||||
"backlog_work_items": "Éléments de travail en backlog",
|
||||
"backlog_work_items": "{entity} en backlog",
|
||||
"active_projects": "Projets actifs",
|
||||
"trend_on_charts": "Tendance sur les graphiques",
|
||||
"all_projects": "Tous les projets",
|
||||
"summary_of_projects": "Résumé des projets",
|
||||
"project_insights": "Aperçus du projet",
|
||||
"started_work_items": "Éléments de travail commencés",
|
||||
"total_work_items": "Total des éléments de travail",
|
||||
"started_work_items": "{entity} commencés",
|
||||
"total_work_items": "Total des {entity}",
|
||||
"total_projects": "Total des projets",
|
||||
"total_admins": "Total des administrateurs",
|
||||
"total_users": "Nombre total d'utilisateurs",
|
||||
"total_intake": "Revenu total",
|
||||
"un_started_work_items": "Éléments de travail non commencés",
|
||||
"un_started_work_items": "{entity} non commencés",
|
||||
"total_guests": "Nombre total d'invités",
|
||||
"completed_work_items": "Éléments de travail terminés"
|
||||
"completed_work_items": "{entity} terminés",
|
||||
"total": "Total des {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Projet} other {Projets}}",
|
||||
@@ -2462,5 +2466,9 @@
|
||||
"last_edited_by": "Dernière modification par",
|
||||
"previously_edited_by": "Précédemment modifié par",
|
||||
"edited_by": "Modifié par"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane n'a pas démarré. Cela pourrait être dû au fait qu'un ou plusieurs services Plane ont échoué à démarrer.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Choisissez View Logs depuis setup.sh et les logs Docker pour en être sûr."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Tidak dapat menghapus proyek dari favorit. Silakan coba lagi.",
|
||||
"project_created_successfully": "Proyek berhasil dibuat",
|
||||
"project_created_successfully_description": "Proyek berhasil dibuat. Anda sekarang dapat mulai menambahkan item kerja ke dalamnya.",
|
||||
"project_name_already_taken": "Nama proyek sudah digunakan",
|
||||
"project_identifier_already_taken": "ID proyek sudah digunakan",
|
||||
"project_cover_image_alt": "Gambar sampul proyek",
|
||||
"name_is_required": "Nama diperlukan",
|
||||
"title_should_be_less_than_255_characters": "Judul harus kurang dari 255 karakter",
|
||||
@@ -866,7 +868,21 @@
|
||||
"view": "Lihat",
|
||||
"deactivated_user": "Pengguna dinonaktifkan",
|
||||
"apply": "Terapkan",
|
||||
"applying": "Terapkan"
|
||||
"applying": "Terapkan",
|
||||
"users": "Pengguna",
|
||||
"admins": "Admin",
|
||||
"guests": "Tamu",
|
||||
"on_track": "Sesuai Jalur",
|
||||
"off_track": "Menyimpang",
|
||||
"at_risk": "Dalam risiko",
|
||||
"timeline": "Linimasa",
|
||||
"completion": "Penyelesaian",
|
||||
"upcoming": "Mendatang",
|
||||
"completed": "Selesai",
|
||||
"in_progress": "Sedang berlangsung",
|
||||
"planned": "Direncanakan",
|
||||
"paused": "Dijedaikan",
|
||||
"no_of": "Jumlah {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "Sumbu-X",
|
||||
@@ -1316,19 +1332,6 @@
|
||||
"custom": "Analitik Kustom"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Lacak kemajuan, beban kerja, dan alokasi. Temukan tren, hilangkan penghalang, dan percepat pekerjaan",
|
||||
"description": "Lihat lingkup dibandingkan permintaan, perkiraan, dan lingkup cree. Dapatkan kinerja oleh anggota tim dan tim, dan pastikan proyek Anda berjalan tepat waktu.",
|
||||
"primary_button": {
|
||||
"text": "Mulai proyek pertama Anda",
|
||||
"comic": {
|
||||
"title": "Analitik bekerja terbaik dengan Siklus + Modul",
|
||||
"description": "Pertama, bagi item kerja Anda ke dalam Siklus dan, jika memungkinkan, kelompokkan item kerja yang menjangkau lebih dari satu siklus ke dalam Modul. Lihat kedua fungsi pada navigasi kiri."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Item pekerjaan yang ditugaskan kepada Anda, dipecah berdasarkan status, akan muncul di sini.",
|
||||
"title": "Belum ada data"
|
||||
@@ -1344,21 +1347,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Dibuat vs Diselesaikan",
|
||||
"customized_insights": "Wawasan yang Disesuaikan",
|
||||
"backlog_work_items": "Item pekerjaan backlog",
|
||||
"backlog_work_items": "{entity} backlog",
|
||||
"active_projects": "Proyek Aktif",
|
||||
"trend_on_charts": "Tren pada grafik",
|
||||
"all_projects": "Semua Proyek",
|
||||
"summary_of_projects": "Ringkasan Proyek",
|
||||
"project_insights": "Wawasan Proyek",
|
||||
"started_work_items": "Item pekerjaan yang telah dimulai",
|
||||
"total_work_items": "Total item pekerjaan",
|
||||
"started_work_items": "{entity} yang telah dimulai",
|
||||
"total_work_items": "Total {entity}",
|
||||
"total_projects": "Total Proyek",
|
||||
"total_admins": "Total Admin",
|
||||
"total_users": "Total Pengguna",
|
||||
"total_intake": "Total Pemasukan",
|
||||
"un_started_work_items": "Item pekerjaan yang belum dimulai",
|
||||
"un_started_work_items": "{entity} yang belum dimulai",
|
||||
"total_guests": "Total Tamu",
|
||||
"completed_work_items": "Item pekerjaan yang telah selesai"
|
||||
"completed_work_items": "{entity} yang telah selesai",
|
||||
"total": "Total {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Proyek} other {Proyek}}",
|
||||
@@ -2456,5 +2460,10 @@
|
||||
"last_edited_by": "Terakhir disunting oleh",
|
||||
"previously_edited_by": "Sebelumnya disunting oleh",
|
||||
"edited_by": "Disunting oleh"
|
||||
}
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane tidak berhasil dimulai. Ini bisa karena satu atau lebih layanan Plane gagal untuk dimulai.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Pilih View Logs dari setup.sh dan log Docker untuk memastikan."
|
||||
},
|
||||
"no_of": "Jumlah {entity}"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Impossibile rimuovere il progetto dai preferiti. Per favore, riprova.",
|
||||
"project_created_successfully": "Progetto creato con successo",
|
||||
"project_created_successfully_description": "Progetto creato con successo. Ora puoi iniziare ad aggiungere elementi di lavoro.",
|
||||
"project_name_already_taken": "Il nome del progetto è già stato utilizzato.",
|
||||
"project_identifier_already_taken": "L'identificatore del progetto è già stato utilizzato.",
|
||||
"project_cover_image_alt": "Immagine di copertina del progetto",
|
||||
"name_is_required": "Il nome è obbligatorio",
|
||||
"title_should_be_less_than_255_characters": "Il titolo deve contenere meno di 255 caratteri",
|
||||
@@ -865,7 +867,21 @@
|
||||
"view": "Visualizza",
|
||||
"deactivated_user": "Utente disattivato",
|
||||
"apply": "Applica",
|
||||
"applying": "Applicazione"
|
||||
"applying": "Applicazione",
|
||||
"users": "Utenti",
|
||||
"admins": "Amministratori",
|
||||
"guests": "Ospiti",
|
||||
"on_track": "In linea",
|
||||
"off_track": "Fuori rotta",
|
||||
"at_risk": "A rischio",
|
||||
"timeline": "Cronologia",
|
||||
"completion": "Completamento",
|
||||
"upcoming": "In arrivo",
|
||||
"completed": "Completato",
|
||||
"in_progress": "In corso",
|
||||
"planned": "Pianificato",
|
||||
"paused": "In pausa",
|
||||
"no_of": "N. di {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "Asse X",
|
||||
@@ -1315,19 +1331,6 @@
|
||||
"custom": "Analisi personalizzata"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Traccia il progresso, i carichi di lavoro e le assegnazioni. Individua tendenze, rimuovi gli ostacoli e accelera il lavoro",
|
||||
"description": "Visualizza l'ambito rispetto alla domanda, le stime e il fenomeno del scope creep. Ottieni le prestazioni dei membri del team e dei team, e assicurati che il tuo progetto rispetti le scadenze.",
|
||||
"primary_button": {
|
||||
"text": "Inizia il tuo primo progetto",
|
||||
"comic": {
|
||||
"title": "Le analisi funzionano meglio con Cicli + Moduli",
|
||||
"description": "Prima, definisci i tuoi elementi di lavoro in cicli e, se puoi, raggruppa quelli che si estendono per più di un ciclo in moduli. Dai un'occhiata ad entrambi nel menu di sinistra."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Gli elementi di lavoro assegnati a te, suddivisi per stato, verranno visualizzati qui.",
|
||||
"title": "Nessun dato disponibile"
|
||||
@@ -1343,21 +1346,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Creato vs Risolto",
|
||||
"customized_insights": "Approfondimenti personalizzati",
|
||||
"backlog_work_items": "Elementi di lavoro nel backlog",
|
||||
"backlog_work_items": "{entity} nel backlog",
|
||||
"active_projects": "Progetti attivi",
|
||||
"trend_on_charts": "Tendenza nei grafici",
|
||||
"all_projects": "Tutti i progetti",
|
||||
"summary_of_projects": "Riepilogo dei progetti",
|
||||
"project_insights": "Approfondimenti sul progetto",
|
||||
"started_work_items": "Elementi di lavoro iniziati",
|
||||
"total_work_items": "Totale elementi di lavoro",
|
||||
"started_work_items": "{entity} iniziati",
|
||||
"total_work_items": "Totale {entity}",
|
||||
"total_projects": "Progetti totali",
|
||||
"total_admins": "Totale amministratori",
|
||||
"total_users": "Totale utenti",
|
||||
"total_intake": "Entrate totali",
|
||||
"un_started_work_items": "Elementi di lavoro non avviati",
|
||||
"un_started_work_items": "{entity} non avviati",
|
||||
"total_guests": "Totale ospiti",
|
||||
"completed_work_items": "Elementi di lavoro completati"
|
||||
"completed_work_items": "{entity} completati",
|
||||
"total": "Totale {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Progetto} other {Progetti}}",
|
||||
@@ -2461,5 +2465,9 @@
|
||||
"last_edited_by": "Ultima modifica di",
|
||||
"previously_edited_by": "Precedentemente modificato da",
|
||||
"edited_by": "Modificato da"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane non si è avviato. Questo potrebbe essere dovuto al fatto che uno o più servizi Plane non sono riusciti ad avviarsi.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Scegli View Logs da setup.sh e dai log Docker per essere sicuro."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "プロジェクトをお気に入りから削除できませんでした。もう一度お試しください。",
|
||||
"project_created_successfully": "プロジェクトが正常に作成されました",
|
||||
"project_created_successfully_description": "プロジェクトが正常に作成されました。作業項目を追加できるようになりました。",
|
||||
"project_name_already_taken": "プロジェクト名は既に使用されています。",
|
||||
"project_identifier_already_taken": "プロジェクト識別子は既に使用されています。",
|
||||
"project_cover_image_alt": "プロジェクトのカバー画像",
|
||||
"name_is_required": "名前は必須です",
|
||||
"title_should_be_less_than_255_characters": "タイトルは255文字未満である必要があります",
|
||||
@@ -867,7 +869,21 @@
|
||||
"view": "ビュー",
|
||||
"deactivated_user": "無効化されたユーザー",
|
||||
"apply": "適用",
|
||||
"applying": "適用中"
|
||||
"applying": "適用中",
|
||||
"users": "ユーザー",
|
||||
"admins": "管理者",
|
||||
"guests": "ゲスト",
|
||||
"on_track": "順調",
|
||||
"off_track": "遅れ",
|
||||
"at_risk": "リスクあり",
|
||||
"timeline": "タイムライン",
|
||||
"completion": "完了",
|
||||
"upcoming": "今後の予定",
|
||||
"completed": "完了",
|
||||
"in_progress": "進行中",
|
||||
"planned": "計画済み",
|
||||
"paused": "一時停止",
|
||||
"no_of": "{entity} の数"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "エックス アクシス",
|
||||
@@ -1317,19 +1333,6 @@
|
||||
"custom": "カスタムアナリティクス"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "進捗、ワークロード、割り当てを追跡。傾向を把握し、ブロッカーを解消して、作業をより速く進めましょう",
|
||||
"description": "スコープと需要、見積もり、スコープクリープを確認できます。チームメンバーとチームのパフォーマンスを把握し、プロジェクトが予定通りに進むようにします。",
|
||||
"primary_button": {
|
||||
"text": "最初のプロジェクトを開始",
|
||||
"comic": {
|
||||
"title": "アナリティクスはサイクル + モジュールで最も効果を発揮",
|
||||
"description": "まず、作業項目をサイクルでタイムボックス化し、可能であれば、複数のサイクルにまたがる作業項目をモジュールにグループ化します。左のナビゲーションで両方を確認してください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "あなたに割り当てられた作業項目は、ステータスごとに分類されてここに表示されます。",
|
||||
"title": "まだデータがありません"
|
||||
@@ -1345,21 +1348,22 @@
|
||||
},
|
||||
"created_vs_resolved": "作成 vs 解決",
|
||||
"customized_insights": "カスタマイズされたインサイト",
|
||||
"backlog_work_items": "バックログの作業項目",
|
||||
"backlog_work_items": "バックログの{entity}",
|
||||
"active_projects": "アクティブなプロジェクト",
|
||||
"trend_on_charts": "グラフの傾向",
|
||||
"all_projects": "すべてのプロジェクト",
|
||||
"summary_of_projects": "プロジェクトの概要",
|
||||
"project_insights": "プロジェクトのインサイト",
|
||||
"started_work_items": "開始された作業項目",
|
||||
"total_work_items": "作業項目の合計",
|
||||
"started_work_items": "開始された{entity}",
|
||||
"total_work_items": "{entity}の合計",
|
||||
"total_projects": "プロジェクト合計",
|
||||
"total_admins": "管理者の合計",
|
||||
"total_users": "ユーザー総数",
|
||||
"total_intake": "総収入",
|
||||
"un_started_work_items": "未開始の作業項目",
|
||||
"un_started_work_items": "未開始の{entity}",
|
||||
"total_guests": "ゲストの合計",
|
||||
"completed_work_items": "完了した作業項目"
|
||||
"completed_work_items": "完了した{entity}",
|
||||
"total": "{entity}の合計"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {プロジェクト} other {プロジェクト}}",
|
||||
@@ -2462,5 +2466,9 @@
|
||||
"last_edited_by": "最終編集者",
|
||||
"previously_edited_by": "以前の編集者",
|
||||
"edited_by": "編集者"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Planeが起動しませんでした。これは1つまたは複数のPlaneサービスの起動に失敗したことが原因である可能性があります。",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "setup.shとDockerログからView Logsを選択して確認してください。"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "프로젝트를 즐겨찾기에서 제거하지 못했습니다. 다시 시도해주세요.",
|
||||
"project_created_successfully": "프로젝트가 성공적으로 생성되었습니다",
|
||||
"project_created_successfully_description": "프로젝트가 성공적으로 생성되었습니다. 이제 작업 항목을 추가할 수 있습니다.",
|
||||
"project_name_already_taken": "프로젝트 이름이 이미 사용 중입니다.",
|
||||
"project_identifier_already_taken": "프로젝트 식별자가 이미 사용 중입니다.",
|
||||
"project_cover_image_alt": "프로젝트 커버 이미지",
|
||||
"name_is_required": "이름이 필요합니다",
|
||||
"title_should_be_less_than_255_characters": "제목은 255자 미만이어야 합니다",
|
||||
@@ -868,7 +870,21 @@
|
||||
"view": "보기",
|
||||
"deactivated_user": "비활성화된 사용자",
|
||||
"apply": "적용",
|
||||
"applying": "적용 중"
|
||||
"applying": "적용 중",
|
||||
"users": "사용자",
|
||||
"admins": "관리자",
|
||||
"guests": "게스트",
|
||||
"on_track": "계획대로 진행 중",
|
||||
"off_track": "계획 이탈",
|
||||
"at_risk": "위험",
|
||||
"timeline": "타임라인",
|
||||
"completion": "완료",
|
||||
"upcoming": "예정된",
|
||||
"completed": "완료됨",
|
||||
"in_progress": "진행 중",
|
||||
"planned": "계획된",
|
||||
"paused": "일시 중지됨",
|
||||
"no_of": "{entity} 수"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "X축",
|
||||
@@ -1318,19 +1334,6 @@
|
||||
"custom": "맞춤형 분석"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "진행 상황, 작업량 및 할당을 추적하세요. 트렌드를 파악하고, 차단 요소를 제거하며, 작업을 더 빠르게 진행하세요",
|
||||
"description": "범위 대 수요, 추정치 및 범위 크리프를 확인하세요. 팀원과 팀의 성과를 확인하고 프로젝트가 제시간에 진행되도록 하세요.",
|
||||
"primary_button": {
|
||||
"text": "첫 번째 프로젝트 시작",
|
||||
"comic": {
|
||||
"title": "분석은 주기 + 모듈과 함께 작동합니다",
|
||||
"description": "먼저 작업 항목을 주기로 시간 상자화하고, 주기를 초과하는 작업 항목을 모듈로 그룹화하세요. 왼쪽 탐색에서 둘 다 확인하세요."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "귀하에게 할당된 작업 항목이 상태별로 나누어 여기에 표시됩니다.",
|
||||
"title": "아직 데이터가 없습니다"
|
||||
@@ -1346,21 +1349,22 @@
|
||||
},
|
||||
"created_vs_resolved": "생성됨 vs 해결됨",
|
||||
"customized_insights": "맞춤형 인사이트",
|
||||
"backlog_work_items": "백로그 작업 항목",
|
||||
"backlog_work_items": "백로그 {entity}",
|
||||
"active_projects": "활성 프로젝트",
|
||||
"trend_on_charts": "차트의 추세",
|
||||
"all_projects": "모든 프로젝트",
|
||||
"summary_of_projects": "프로젝트 요약",
|
||||
"project_insights": "프로젝트 인사이트",
|
||||
"started_work_items": "시작된 작업 항목",
|
||||
"total_work_items": "총 작업 항목",
|
||||
"started_work_items": "시작된 {entity}",
|
||||
"total_work_items": "총 {entity}",
|
||||
"total_projects": "총 프로젝트 수",
|
||||
"total_admins": "총 관리자 수",
|
||||
"total_users": "총 사용자 수",
|
||||
"total_intake": "총 수입",
|
||||
"un_started_work_items": "시작되지 않은 작업 항목",
|
||||
"un_started_work_items": "시작되지 않은 {entity}",
|
||||
"total_guests": "총 게스트 수",
|
||||
"completed_work_items": "완료된 작업 항목"
|
||||
"completed_work_items": "완료된 {entity}",
|
||||
"total": "총 {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {프로젝트} other {프로젝트}}",
|
||||
@@ -2464,5 +2468,9 @@
|
||||
"last_edited_by": "마지막 편집자",
|
||||
"previously_edited_by": "이전 편집자",
|
||||
"edited_by": "편집자"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane이 시작되지 않았습니다. 이는 하나 이상의 Plane 서비스가 시작에 실패했기 때문일 수 있습니다.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "확실히 하려면 setup.sh와 Docker 로그에서 View Logs를 선택하세요."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Nie udało się usunąć projektu z ulubionych. Spróbuj ponownie.",
|
||||
"project_created_successfully": "Projekt utworzono pomyślnie",
|
||||
"project_created_successfully_description": "Projekt został pomyślnie utworzony. Teraz możesz dodawać elementy pracy.",
|
||||
"project_name_already_taken": "Nazwa projektu jest już zajęta.",
|
||||
"project_identifier_already_taken": "Identyfikator projektu jest już zajęty.",
|
||||
"project_cover_image_alt": "Obraz w tle projektu",
|
||||
"name_is_required": "Nazwa jest wymagana",
|
||||
"title_should_be_less_than_255_characters": "Nazwa musi mieć mniej niż 255 znaków",
|
||||
@@ -868,7 +870,21 @@
|
||||
"view": "Widok",
|
||||
"deactivated_user": "Dezaktywowany użytkownik",
|
||||
"apply": "Zastosuj",
|
||||
"applying": "Zastosowanie"
|
||||
"applying": "Zastosowanie",
|
||||
"users": "Użytkownicy",
|
||||
"admins": "Administratorzy",
|
||||
"guests": "Goście",
|
||||
"on_track": "Na dobrej drodze",
|
||||
"off_track": "Poza planem",
|
||||
"at_risk": "W zagrożeniu",
|
||||
"timeline": "Oś czasu",
|
||||
"completion": "Zakończenie",
|
||||
"upcoming": "Nadchodzące",
|
||||
"completed": "Zakończone",
|
||||
"in_progress": "W trakcie",
|
||||
"planned": "Zaplanowane",
|
||||
"paused": "Wstrzymane",
|
||||
"no_of": "Liczba {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "Oś X",
|
||||
@@ -1318,19 +1334,6 @@
|
||||
"custom": "Analizy niestandardowe"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Śledź postępy, obciążenie i alokacje. Identyfikuj trendy, usuwaj przeszkody i przyspieszaj pracę",
|
||||
"description": "Obserwuj zakres vs. zapotrzebowanie, szacunki i zakres. Sprawdzaj wydajność członków i zespołów, upewnij się, że projekty kończą się na czas.",
|
||||
"primary_button": {
|
||||
"text": "Zacznij pierwszy projekt",
|
||||
"comic": {
|
||||
"title": "Analizy najlepiej działają z Cyklem + Modułami",
|
||||
"description": "Najpierw ogranicz pracę w cyklach i grupuj zadania w modułach obejmujących wiele cykli. Znajdziesz je w menu po lewej."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Przypisane do Ciebie elementy pracy, podzielone według stanu, pojawią się tutaj.",
|
||||
"title": "Brak danych"
|
||||
@@ -1346,21 +1349,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Utworzone vs Rozwiązane",
|
||||
"customized_insights": "Dostosowane informacje",
|
||||
"backlog_work_items": "Elementy pracy w backlogu",
|
||||
"backlog_work_items": "{entity} w backlogu",
|
||||
"active_projects": "Aktywne projekty",
|
||||
"trend_on_charts": "Trend na wykresach",
|
||||
"all_projects": "Wszystkie projekty",
|
||||
"summary_of_projects": "Podsumowanie projektów",
|
||||
"project_insights": "Wgląd w projekt",
|
||||
"started_work_items": "Rozpoczęte elementy pracy",
|
||||
"total_work_items": "Łączna liczba elementów pracy",
|
||||
"started_work_items": "Rozpoczęte {entity}",
|
||||
"total_work_items": "Łączna liczba {entity}",
|
||||
"total_projects": "Łączna liczba projektów",
|
||||
"total_admins": "Łączna liczba administratorów",
|
||||
"total_users": "Łączna liczba użytkowników",
|
||||
"total_intake": "Całkowity dochód",
|
||||
"un_started_work_items": "Nierozpoczęte elementy pracy",
|
||||
"un_started_work_items": "Nierozpoczęte {entity}",
|
||||
"total_guests": "Łączna liczba gości",
|
||||
"completed_work_items": "Ukończone elementy pracy"
|
||||
"completed_work_items": "Ukończone {entity}",
|
||||
"total": "Łączna liczba {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Projekt} few {Projekty} other {Projektów}}",
|
||||
@@ -2463,5 +2467,9 @@
|
||||
"last_edited_by": "Ostatnio edytowane przez",
|
||||
"previously_edited_by": "Wcześniej edytowane przez",
|
||||
"edited_by": "Edytowane przez"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane nie uruchomił się. Może to być spowodowane tym, że jedna lub więcej usług Plane nie mogła się uruchomić.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Wybierz View Logs z setup.sh i logów Docker, aby mieć pewność."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Não foi possível remover o projeto dos favoritos. Por favor, tente novamente.",
|
||||
"project_created_successfully": "Projeto criado com sucesso",
|
||||
"project_created_successfully_description": "Projeto criado com sucesso. Agora você pode começar a adicionar itens de trabalho a ele.",
|
||||
"project_name_already_taken": "O nome do projeto já está em uso.",
|
||||
"project_identifier_already_taken": "O identificador do projeto já está em uso.",
|
||||
"project_cover_image_alt": "Imagem de capa do projeto",
|
||||
"name_is_required": "Nome é obrigatório",
|
||||
"title_should_be_less_than_255_characters": "O título deve ter menos de 255 caracteres",
|
||||
@@ -868,7 +870,21 @@
|
||||
"view": "Visualizar",
|
||||
"deactivated_user": "Usuário desativado",
|
||||
"apply": "Aplicar",
|
||||
"applying": "Aplicando"
|
||||
"applying": "Aplicando",
|
||||
"users": "Usuários",
|
||||
"admins": "Administradores",
|
||||
"guests": "Convidados",
|
||||
"on_track": "No caminho certo",
|
||||
"off_track": "Fora do caminho",
|
||||
"at_risk": "Em risco",
|
||||
"timeline": "Linha do tempo",
|
||||
"completion": "Conclusão",
|
||||
"upcoming": "Próximo",
|
||||
"completed": "Concluído",
|
||||
"in_progress": "Em andamento",
|
||||
"planned": "Planejado",
|
||||
"paused": "Pausado",
|
||||
"no_of": "Nº de {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "Eixo X",
|
||||
@@ -1318,19 +1334,6 @@
|
||||
"custom": "Análises Personalizadas"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Acompanhe o progresso, as cargas de trabalho e as alocações. Identifique tendências, remova bloqueadores e mova o trabalho mais rapidamente",
|
||||
"description": "Veja o escopo versus a demanda, as estimativas e o aumento do escopo. Obtenha o desempenho por membros da equipe e equipes, e certifique-se de que seu projeto seja executado no prazo.",
|
||||
"primary_button": {
|
||||
"text": "Comece seu primeiro projeto",
|
||||
"comic": {
|
||||
"title": "A análise funciona melhor com Ciclos + Módulos",
|
||||
"description": "Primeiro, coloque seus itens de trabalho em Ciclos e, se puder, agrupe os itens de trabalho que abrangem mais de um ciclo em Módulos. Confira ambos na navegação à esquerda."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Os itens de trabalho atribuídos a você, divididos por estado, aparecerão aqui.",
|
||||
"title": "Ainda não há dados"
|
||||
@@ -1346,21 +1349,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Criado vs Resolvido",
|
||||
"customized_insights": "Insights personalizados",
|
||||
"backlog_work_items": "Itens de trabalho no backlog",
|
||||
"backlog_work_items": "{entity} no backlog",
|
||||
"active_projects": "Projetos ativos",
|
||||
"trend_on_charts": "Tendência nos gráficos",
|
||||
"all_projects": "Todos os projetos",
|
||||
"summary_of_projects": "Resumo dos projetos",
|
||||
"project_insights": "Insights do projeto",
|
||||
"started_work_items": "Itens de trabalho iniciados",
|
||||
"total_work_items": "Total de itens de trabalho",
|
||||
"started_work_items": "{entity} iniciados",
|
||||
"total_work_items": "Total de {entity}",
|
||||
"total_projects": "Total de projetos",
|
||||
"total_admins": "Total de administradores",
|
||||
"total_users": "Total de usuários",
|
||||
"total_intake": "Receita total",
|
||||
"un_started_work_items": "Itens de trabalho não iniciados",
|
||||
"un_started_work_items": "{entity} não iniciados",
|
||||
"total_guests": "Total de convidados",
|
||||
"completed_work_items": "Itens de trabalho concluídos"
|
||||
"completed_work_items": "{entity} concluídos",
|
||||
"total": "Total de {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Projeto} other {Projetos}}",
|
||||
@@ -2458,5 +2462,9 @@
|
||||
"last_edited_by": "Última edição por",
|
||||
"previously_edited_by": "Anteriormente editado por",
|
||||
"edited_by": "Editado por"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "O Plane não inicializou. Isso pode ser porque um ou mais serviços do Plane falharam ao iniciar.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Escolha View Logs do setup.sh e logs do Docker para ter certeza."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Nu s-a putut elimina proiectul din favorite. Încearcă din nou.",
|
||||
"project_created_successfully": "Proiect creat cu succes",
|
||||
"project_created_successfully_description": "Proiect creat cu succes. Poți începe să adaugi activități în el.",
|
||||
"project_name_already_taken": "Numele proiectului este deja folosit.",
|
||||
"project_identifier_already_taken": "Identificatorul proiectului este deja folosit.",
|
||||
"project_cover_image_alt": "Coperta proiectului",
|
||||
"name_is_required": "Numele este obligatoriu",
|
||||
"title_should_be_less_than_255_characters": "Titlul trebuie să conțină mai puțin de 255 de caractere",
|
||||
@@ -866,7 +868,21 @@
|
||||
"view": "Vizualizează",
|
||||
"deactivated_user": "Utilizator dezactivat",
|
||||
"apply": "Aplică",
|
||||
"applying": "Aplicând"
|
||||
"applying": "Aplicând",
|
||||
"users": "Utilizatori",
|
||||
"admins": "Administratori",
|
||||
"guests": "Invitați",
|
||||
"on_track": "Pe drumul cel bun",
|
||||
"off_track": "În afara traiectoriei",
|
||||
"at_risk": "În pericol",
|
||||
"timeline": "Cronologie",
|
||||
"completion": "Finalizare",
|
||||
"upcoming": "Viitor",
|
||||
"completed": "Finalizat",
|
||||
"in_progress": "În desfășurare",
|
||||
"planned": "Planificat",
|
||||
"paused": "Pauzat",
|
||||
"no_of": "Nr. de {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "axa-X",
|
||||
@@ -1316,19 +1332,6 @@
|
||||
"custom": "Analitice personalizate"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Urmărește progresul, activitățile și alocările. Observă tendințele, elimină blocajele și accelerează munca",
|
||||
"description": "Vezi raportul dintre activitățile asumate și cerere, estimările și eventualele extinderi neplanificate ale activităților asumate. Obține performanța pe membri și echipe și asigură-te că proiectul tău se încadrează în timp.",
|
||||
"primary_button": {
|
||||
"text": "Începe primul tău proiect",
|
||||
"comic": {
|
||||
"title": "Statisticile funcționează cel mai bine cu Cicluri + Module",
|
||||
"description": "Mai întâi, încadrează-ți activitățile în Cicluri și, dacă poți, grupează-le pe cele care se întind pe mai multe cicluri în Module. Le găsești în meniul din stânga."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Elementele de lucru atribuite ție, împărțite pe stări, vor apărea aici.",
|
||||
"title": "Nu există date încă"
|
||||
@@ -1344,21 +1347,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Creat vs Rezolvat",
|
||||
"customized_insights": "Perspective personalizate",
|
||||
"backlog_work_items": "Elemente de lucru din backlog",
|
||||
"backlog_work_items": "{entity} din backlog",
|
||||
"active_projects": "Proiecte active",
|
||||
"trend_on_charts": "Tendință în grafice",
|
||||
"all_projects": "Toate proiectele",
|
||||
"summary_of_projects": "Sumarul proiectelor",
|
||||
"project_insights": "Informații despre proiect",
|
||||
"started_work_items": "Elemente de lucru începute",
|
||||
"total_work_items": "Totalul elementelor de lucru",
|
||||
"started_work_items": "{entity} începute",
|
||||
"total_work_items": "Totalul {entity}",
|
||||
"total_projects": "Total proiecte",
|
||||
"total_admins": "Total administratori",
|
||||
"total_users": "Total utilizatori",
|
||||
"total_intake": "Venit total",
|
||||
"un_started_work_items": "Elemente de lucru neîncepute",
|
||||
"un_started_work_items": "{entity} neîncepute",
|
||||
"total_guests": "Total invitați",
|
||||
"completed_work_items": "Elemente de lucru finalizate"
|
||||
"completed_work_items": "{entity} finalizate",
|
||||
"total": "Totalul {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Proiect} other {Proiecte}}",
|
||||
@@ -2456,5 +2460,9 @@
|
||||
"last_edited_by": "Ultima editare de către",
|
||||
"previously_edited_by": "Editat anterior de către",
|
||||
"edited_by": "Editat de"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane nu a pornit. Aceasta ar putea fi din cauza că unul sau mai multe servicii Plane au eșuat să pornească.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Alegeți View Logs din setup.sh și logurile Docker pentru a fi siguri."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Не удалось удалить проект из избранного. Попробуйте снова.",
|
||||
"project_created_successfully": "Проект успешно создан",
|
||||
"project_created_successfully_description": "Проект успешно создан. Теперь вы можете добавлять рабочие элементы.",
|
||||
"project_name_already_taken": "Имя проекта уже используется.",
|
||||
"project_identifier_already_taken": "Идентификатор проекта уже используется.",
|
||||
"project_cover_image_alt": "Обложка проекта",
|
||||
"name_is_required": "Требуется имя",
|
||||
"title_should_be_less_than_255_characters": "Заголовок должен быть короче 255 символов",
|
||||
@@ -868,7 +870,21 @@
|
||||
"view": "Просмотр",
|
||||
"deactivated_user": "Деактивированный пользователь",
|
||||
"apply": "Применить",
|
||||
"applying": "Применение"
|
||||
"applying": "Применение",
|
||||
"users": "Пользователи",
|
||||
"admins": "Администраторы",
|
||||
"guests": "Гости",
|
||||
"on_track": "По плану",
|
||||
"off_track": "Отклонение от плана",
|
||||
"at_risk": "Под угрозой",
|
||||
"timeline": "Хронология",
|
||||
"completion": "Завершение",
|
||||
"upcoming": "Предстоящие",
|
||||
"completed": "Завершено",
|
||||
"in_progress": "В процессе",
|
||||
"planned": "Запланировано",
|
||||
"paused": "На паузе",
|
||||
"no_of": "Количество {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "Ось X",
|
||||
@@ -1318,19 +1334,6 @@
|
||||
"custom": "Пользовательская аналитика"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Отслеживайте прогресс, загрузку и распределение ресурсов",
|
||||
"description": "Анализируйте объёмы работ, оценивайте сроки и контролируйте выполнение проектов. Отслеживайте производительность команды и соблюдайте сроки.",
|
||||
"primary_button": {
|
||||
"text": "Начать первый проект",
|
||||
"comic": {
|
||||
"title": "Аналитика лучше всего работает с Циклами + Модулями",
|
||||
"description": "Сначала группируйте рабочие элементы в Циклы, а при возможности - объединяйте рабочие элементы в Модули. Найдите оба раздела в левом меню."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Назначенные вам рабочие элементы, разбитые по статусам, появятся здесь.",
|
||||
"title": "Данных пока нет"
|
||||
@@ -1346,21 +1349,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Создано vs Решено",
|
||||
"customized_insights": "Индивидуальные аналитические данные",
|
||||
"backlog_work_items": "Элементы работы в бэклоге",
|
||||
"backlog_work_items": "{entity} в бэклоге",
|
||||
"active_projects": "Активные проекты",
|
||||
"trend_on_charts": "Тренд на графиках",
|
||||
"all_projects": "Все проекты",
|
||||
"summary_of_projects": "Сводка по проектам",
|
||||
"project_insights": "Аналитика проекта",
|
||||
"started_work_items": "Начатые рабочие элементы",
|
||||
"total_work_items": "Общее количество рабочих элементов",
|
||||
"started_work_items": "Начатые {entity}",
|
||||
"total_work_items": "Общее количество {entity}",
|
||||
"total_projects": "Всего проектов",
|
||||
"total_admins": "Всего администраторов",
|
||||
"total_users": "Всего пользователей",
|
||||
"total_intake": "Общий доход",
|
||||
"un_started_work_items": "Не начатые рабочие элементы",
|
||||
"un_started_work_items": "Не начатые {entity}",
|
||||
"total_guests": "Всего гостей",
|
||||
"completed_work_items": "Завершённые рабочие элементы"
|
||||
"completed_work_items": "Завершённые {entity}",
|
||||
"total": "Общее количество {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Проект} other {Проекты}}",
|
||||
@@ -2464,5 +2468,10 @@
|
||||
"last_edited_by": "Последнее редактирование",
|
||||
"previously_edited_by": "Ранее отредактировано",
|
||||
"edited_by": "Отредактировано"
|
||||
}
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane не запустился. Это может быть из-за того, что один или несколько сервисов Plane не смогли запуститься.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Выберите View Logs из setup.sh и логов Docker, чтобы убедиться."
|
||||
},
|
||||
"no_of": "Количество {entity}"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Nepodarilo sa odstrániť projekt z obľúbených. Skúste to prosím znova.",
|
||||
"project_created_successfully": "Projekt bol úspešne vytvorený",
|
||||
"project_created_successfully_description": "Projekt bol úspešne vytvorený. Teraz môžete začať pridávať pracovné položky.",
|
||||
"project_name_already_taken": "Názov projektu je už použitý.",
|
||||
"project_identifier_already_taken": "Identifikátor projektu je už použitý.",
|
||||
"project_cover_image_alt": "Úvodný obrázok projektu",
|
||||
"name_is_required": "Názov je povinný",
|
||||
"title_should_be_less_than_255_characters": "Názov by mal byť kratší ako 255 znakov",
|
||||
@@ -868,7 +870,21 @@
|
||||
"view": "Zobraziť",
|
||||
"deactivated_user": "Deaktivovaný používateľ",
|
||||
"apply": "Použiť",
|
||||
"applying": "Používanie"
|
||||
"applying": "Používanie",
|
||||
"users": "Používatelia",
|
||||
"admins": "Administrátori",
|
||||
"guests": "Hostia",
|
||||
"on_track": "Na správnej ceste",
|
||||
"off_track": "Mimo plán",
|
||||
"at_risk": "V ohrození",
|
||||
"timeline": "Časová os",
|
||||
"completion": "Dokončenie",
|
||||
"upcoming": "Nadchádzajúce",
|
||||
"completed": "Dokončené",
|
||||
"in_progress": "Prebieha",
|
||||
"planned": "Plánované",
|
||||
"paused": "Pozastavené",
|
||||
"no_of": "Počet {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "Os X",
|
||||
@@ -1318,19 +1334,6 @@
|
||||
"custom": "Vlastná analytika"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Sledujte pokrok, vyťaženie a alokácie. Identifikujte trendy, odstráňte prekážky a zrýchlite prácu",
|
||||
"description": "Sledujte rozsah vs. dopyt, odhady a rozsah. Zistite výkonnosť členov a tímov, zabezpečte včasné dokončenie projektov.",
|
||||
"primary_button": {
|
||||
"text": "Začnite prvý projekt",
|
||||
"comic": {
|
||||
"title": "Analytika funguje najlepšie s Cykly + Moduly",
|
||||
"description": "Najprv časovo ohraničte prácu do cyklov a zoskupte položky presahujúce cyklus do modulov. Nájdete ich v ľavom menu."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Pracovné položky priradené vám, rozdelené podľa stavu, sa zobrazia tu.",
|
||||
"title": "Zatiaľ žiadne údaje"
|
||||
@@ -1346,21 +1349,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Vytvorené vs Vyriešené",
|
||||
"customized_insights": "Prispôsobené prehľady",
|
||||
"backlog_work_items": "Pracovné položky v backlogu",
|
||||
"backlog_work_items": "{entity} v backlogu",
|
||||
"active_projects": "Aktívne projekty",
|
||||
"trend_on_charts": "Trend na grafoch",
|
||||
"all_projects": "Všetky projekty",
|
||||
"summary_of_projects": "Súhrn projektov",
|
||||
"project_insights": "Prehľad projektu",
|
||||
"started_work_items": "Spustené pracovné položky",
|
||||
"total_work_items": "Celkový počet pracovných položiek",
|
||||
"started_work_items": "Spustené {entity}",
|
||||
"total_work_items": "Celkový počet {entity}",
|
||||
"total_projects": "Celkový počet projektov",
|
||||
"total_admins": "Celkový počet administrátorov",
|
||||
"total_users": "Celkový počet používateľov",
|
||||
"total_intake": "Celkový príjem",
|
||||
"un_started_work_items": "Nespustené pracovné položky",
|
||||
"un_started_work_items": "Nespustené {entity}",
|
||||
"total_guests": "Celkový počet hostí",
|
||||
"completed_work_items": "Dokončené pracovné položky"
|
||||
"completed_work_items": "Dokončené {entity}",
|
||||
"total": "Celkový počet {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Projekt} few {Projekty} other {Projektov}}",
|
||||
@@ -2463,5 +2467,9 @@
|
||||
"last_edited_by": "Naposledy upravené používateľom",
|
||||
"previously_edited_by": "Predtým upravené používateľom",
|
||||
"edited_by": "Upravené používateľom"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane sa nespustil. Toto môže byť spôsobené tým, že sa jedna alebo viac služieb Plane nepodarilo spustiť.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Vyberte View Logs z setup.sh a Docker logov, aby ste si boli istí."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Proje favorilerden kaldırılamadı. Lütfen tekrar deneyin.",
|
||||
"project_created_successfully": "Proje başarıyla oluşturuldu",
|
||||
"project_created_successfully_description": "Proje başarıyla oluşturuldu. Artık iş öğeleri eklemeye başlayabilirsiniz.",
|
||||
"project_name_already_taken": "Proje ismi zaten kullanılıyor.",
|
||||
"project_identifier_already_taken": "Proje kimliği zaten kullanılıyor.",
|
||||
"project_cover_image_alt": "Proje kapak resmi",
|
||||
"name_is_required": "Ad gereklidir",
|
||||
"title_should_be_less_than_255_characters": "Başlık 255 karakterden az olmalı",
|
||||
@@ -869,7 +871,21 @@
|
||||
"view": "Görünüm",
|
||||
"deactivated_user": "Devre dışı bırakılmış kullanıcı",
|
||||
"apply": "Uygula",
|
||||
"applying": "Uygulanıyor"
|
||||
"applying": "Uygulanıyor",
|
||||
"users": "Kullanıcılar",
|
||||
"admins": "Yöneticiler",
|
||||
"guests": "Misafirler",
|
||||
"on_track": "Yolunda",
|
||||
"off_track": "Yolunda değil",
|
||||
"at_risk": "Risk altında",
|
||||
"timeline": "Zaman çizelgesi",
|
||||
"completion": "Tamamlama",
|
||||
"upcoming": "Yaklaşan",
|
||||
"completed": "Tamamlandı",
|
||||
"in_progress": "Devam ediyor",
|
||||
"planned": "Planlandı",
|
||||
"paused": "Durduruldu",
|
||||
"no_of": "{entity} sayısı"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "X ekseni",
|
||||
@@ -1319,19 +1335,6 @@
|
||||
"custom": "Özel Analitik"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "İlerlemeyi, iş yükünü ve tahsisatları izleyin. Eğilimleri tespit edin, engelleri kaldırın ve işleri hızlandırın",
|
||||
"description": "Kapsam ve talep, tahminler ve kapsam genişlemesini görün. Takım üyeleri ve ekiplerin performansını izleyin ve projenizin zamanında ilerlemesini sağlayın.",
|
||||
"primary_button": {
|
||||
"text": "İlk projenizi başlatın",
|
||||
"comic": {
|
||||
"title": "Analitik Döngüler + Modüllerle en iyi şekilde çalışır",
|
||||
"description": "Öncelikle, iş öğelerinizi Döngülere zamanlayın ve mümkünse, bir döngüden uzun süren iş öğelerini Modüllerde gruplayın. Her ikisini de sol gezintide bulabilirsiniz."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Size atanan iş öğeleri, duruma göre ayrılarak burada gösterilecektir.",
|
||||
"title": "Henüz veri yok"
|
||||
@@ -1347,21 +1350,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Oluşturulan vs Çözülen",
|
||||
"customized_insights": "Özelleştirilmiş İçgörüler",
|
||||
"backlog_work_items": "Backlog iş öğeleri",
|
||||
"backlog_work_items": "Backlog {entity}",
|
||||
"active_projects": "Aktif Projeler",
|
||||
"trend_on_charts": "Grafiklerdeki eğilim",
|
||||
"all_projects": "Tüm Projeler",
|
||||
"summary_of_projects": "Projelerin Özeti",
|
||||
"project_insights": "Proje İçgörüleri",
|
||||
"started_work_items": "Başlatılan iş öğeleri",
|
||||
"total_work_items": "Toplam iş öğesi",
|
||||
"started_work_items": "Başlatılan {entity}",
|
||||
"total_work_items": "Toplam {entity}",
|
||||
"total_projects": "Toplam Proje",
|
||||
"total_admins": "Toplam Yönetici",
|
||||
"total_users": "Toplam Kullanıcı",
|
||||
"total_intake": "Toplam Gelir",
|
||||
"un_started_work_items": "Başlanmamış iş öğeleri",
|
||||
"un_started_work_items": "Başlanmamış {entity}",
|
||||
"total_guests": "Toplam Misafir",
|
||||
"completed_work_items": "Tamamlanmış iş öğeleri"
|
||||
"completed_work_items": "Tamamlanmış {entity}",
|
||||
"total": "Toplam {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Proje} other {Projeler}}",
|
||||
@@ -2442,5 +2446,9 @@
|
||||
"last_edited_by": "Son düzenleyen",
|
||||
"previously_edited_by": "Önceki düzenleyen",
|
||||
"edited_by": "Tarafından düzenlendi"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane başlatılamadı. Bu, bir veya daha fazla Plane servisinin başlatılamaması nedeniyle olabilir.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Emin olmak için setup.sh ve Docker loglarından View Logs'u seçin."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Не вдалося видалити проєкт із вибраного. Спробуйте ще раз.",
|
||||
"project_created_successfully": "Проєкт успішно створено",
|
||||
"project_created_successfully_description": "Проєкт успішно створений. Тепер ви можете почати додавати робочі одиниці.",
|
||||
"project_name_already_taken": "Назва проекту вже використовується.",
|
||||
"project_identifier_already_taken": "Ідентифікатор проекту вже використовується.",
|
||||
"project_cover_image_alt": "Обкладинка проєкту",
|
||||
"name_is_required": "Назва є обов’язковою",
|
||||
"title_should_be_less_than_255_characters": "Назва має бути коротшою за 255 символів",
|
||||
@@ -868,7 +870,21 @@
|
||||
"view": "Подання",
|
||||
"deactivated_user": "Деактивований користувач",
|
||||
"apply": "Застосувати",
|
||||
"applying": "Застосовується"
|
||||
"applying": "Застосовується",
|
||||
"users": "Користувачі",
|
||||
"admins": "Адміністратори",
|
||||
"guests": "Гості",
|
||||
"on_track": "У межах графіку",
|
||||
"off_track": "Поза графіком",
|
||||
"at_risk": "Під загрозою",
|
||||
"timeline": "Хронологія",
|
||||
"completion": "Завершення",
|
||||
"upcoming": "Майбутнє",
|
||||
"completed": "Завершено",
|
||||
"in_progress": "В процесі",
|
||||
"planned": "Заплановано",
|
||||
"paused": "Призупинено",
|
||||
"no_of": "Кількість {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "Вісь X",
|
||||
@@ -1318,19 +1334,6 @@
|
||||
"custom": "Користувацька аналітика"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Відстежуйте прогрес, навантаження й розподіл. Виявляйте тенденції, усувайте перешкоди й прискорюйте роботу",
|
||||
"description": "Стежте за обсягом проти попиту, оцінками та обсягом. Визначайте ефективність учасників і команд, аби вчасно виконувати проєкти.",
|
||||
"primary_button": {
|
||||
"text": "Розпочніть перший проєкт",
|
||||
"comic": {
|
||||
"title": "Аналітика найкраще працює з Циклами + Модулями",
|
||||
"description": "Спочатку обмежте роботу в часі через Цикли та згрупуйте робочі одиниці, які тривають довше, у Модулі. Все це в лівому меню."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Призначені вам робочі елементи, розбиті за станом, з’являться тут.",
|
||||
"title": "Ще немає даних"
|
||||
@@ -1346,21 +1349,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Створено vs Вирішено",
|
||||
"customized_insights": "Персоналізовані аналітичні дані",
|
||||
"backlog_work_items": "Робочі елементи у беклозі",
|
||||
"backlog_work_items": "{entity} у беклозі",
|
||||
"active_projects": "Активні проєкти",
|
||||
"trend_on_charts": "Тенденція на графіках",
|
||||
"all_projects": "Усі проєкти",
|
||||
"summary_of_projects": "Зведення проєктів",
|
||||
"project_insights": "Аналітика проєкту",
|
||||
"started_work_items": "Розпочаті робочі елементи",
|
||||
"total_work_items": "Усього робочих елементів",
|
||||
"started_work_items": "Розпочаті {entity}",
|
||||
"total_work_items": "Усього {entity}",
|
||||
"total_projects": "Усього проєктів",
|
||||
"total_admins": "Усього адміністраторів",
|
||||
"total_users": "Усього користувачів",
|
||||
"total_intake": "Загальний дохід",
|
||||
"un_started_work_items": "Нерозпочаті робочі елементи",
|
||||
"un_started_work_items": "Нерозпочаті {entity}",
|
||||
"total_guests": "Усього гостей",
|
||||
"completed_work_items": "Завершені робочі елементи"
|
||||
"completed_work_items": "Завершені {entity}",
|
||||
"total": "Усього {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {Проєкт} few {Проєкти} other {Проєктів}}",
|
||||
@@ -2463,5 +2467,9 @@
|
||||
"last_edited_by": "Останнє редагування",
|
||||
"previously_edited_by": "Раніше відредаговано",
|
||||
"edited_by": "Відредаговано"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane не запустився. Це може бути через те, що один або декілька сервісів Plane не змогли запуститися.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Виберіть View Logs з setup.sh та логів Docker, щоб переконатися."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -316,6 +316,8 @@
|
||||
"failed_to_remove_project_from_favorites": "Không thể xóa dự án khỏi mục yêu thích. Vui lòng thử lại.",
|
||||
"project_created_successfully": "Dự án đã được tạo thành công",
|
||||
"project_created_successfully_description": "Dự án đã được tạo thành công. Bây giờ bạn có thể bắt đầu thêm mục công việc.",
|
||||
"project_name_already_taken": "Tên dự án đã được sử dụng.",
|
||||
"project_identifier_already_taken": "ID dự án đã được sử dụng.",
|
||||
"project_cover_image_alt": "Ảnh bìa dự án",
|
||||
"name_is_required": "Tên là bắt buộc",
|
||||
"title_should_be_less_than_255_characters": "Tiêu đề phải ít hơn 255 ký tự",
|
||||
@@ -867,7 +869,21 @@
|
||||
"view": "Xem",
|
||||
"deactivated_user": "Người dùng bị vô hiệu hóa",
|
||||
"apply": "Áp dụng",
|
||||
"applying": "Đang áp dụng"
|
||||
"applying": "Đang áp dụng",
|
||||
"users": "Người dùng",
|
||||
"admins": "Quản trị viên",
|
||||
"guests": "Khách",
|
||||
"on_track": "Đúng tiến độ",
|
||||
"off_track": "Chệch hướng",
|
||||
"at_risk": "Có nguy cơ",
|
||||
"timeline": "Dòng thời gian",
|
||||
"completion": "Hoàn thành",
|
||||
"upcoming": "Sắp tới",
|
||||
"completed": "Đã hoàn thành",
|
||||
"in_progress": "Đang tiến hành",
|
||||
"planned": "Đã lên kế hoạch",
|
||||
"paused": "Tạm dừng",
|
||||
"no_of": "Số lượng {entity}"
|
||||
},
|
||||
"chart": {
|
||||
"x_axis": "Trục X",
|
||||
@@ -1317,19 +1333,6 @@
|
||||
"custom": "Phân tích tùy chỉnh"
|
||||
},
|
||||
"empty_state": {
|
||||
"general": {
|
||||
"title": "Theo dõi tiến độ, khối lượng công việc và phân công. Khám phá xu hướng, loại bỏ rào cản và đẩy nhanh công việc",
|
||||
"description": "Xem phạm vi so với nhu cầu, ước tính và mở rộng phạm vi. Nhận hiệu suất của thành viên nhóm và nhóm, đảm bảo dự án của bạn đúng tiến độ.",
|
||||
"primary_button": {
|
||||
"text": "Bắt đầu dự án đầu tiên của bạn",
|
||||
"comic": {
|
||||
"title": "Phân tích hoạt động tốt nhất trong chu kỳ + mô-đun",
|
||||
"description": "Đầu tiên, giới hạn mục công việc của bạn trong chu kỳ và nếu có thể, nhóm mục công việc kéo dài nhiều chu kỳ thành mô-đun. Xem cả hai trong thanh điều hướng bên trái."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state_v2": {
|
||||
"customized_insights": {
|
||||
"description": "Các hạng mục công việc được giao cho bạn, phân loại theo trạng thái, sẽ hiển thị tại đây.",
|
||||
"title": "Chưa có dữ liệu"
|
||||
@@ -1345,21 +1348,22 @@
|
||||
},
|
||||
"created_vs_resolved": "Đã tạo vs Đã giải quyết",
|
||||
"customized_insights": "Thông tin chi tiết tùy chỉnh",
|
||||
"backlog_work_items": "Các hạng mục công việc tồn đọng",
|
||||
"backlog_work_items": "{entity} tồn đọng",
|
||||
"active_projects": "Dự án đang hoạt động",
|
||||
"trend_on_charts": "Xu hướng trên biểu đồ",
|
||||
"all_projects": "Tất cả dự án",
|
||||
"summary_of_projects": "Tóm tắt dự án",
|
||||
"project_insights": "Thông tin chi tiết dự án",
|
||||
"started_work_items": "Hạng mục công việc đã bắt đầu",
|
||||
"total_work_items": "Tổng số hạng mục công việc",
|
||||
"started_work_items": "{entity} đã bắt đầu",
|
||||
"total_work_items": "Tổng số {entity}",
|
||||
"total_projects": "Tổng số dự án",
|
||||
"total_admins": "Tổng số quản trị viên",
|
||||
"total_users": "Tổng số người dùng",
|
||||
"total_intake": "Tổng thu",
|
||||
"un_started_work_items": "Hạng mục công việc chưa bắt đầu",
|
||||
"un_started_work_items": "{entity} chưa bắt đầu",
|
||||
"total_guests": "Tổng số khách",
|
||||
"completed_work_items": "Hạng mục công việc đã hoàn thành"
|
||||
"completed_work_items": "{entity} đã hoàn thành",
|
||||
"total": "Tổng số {entity}"
|
||||
},
|
||||
"workspace_projects": {
|
||||
"label": "{count, plural, one {dự án} other {dự án}}",
|
||||
@@ -2461,5 +2465,9 @@
|
||||
"last_edited_by": "Chỉnh sửa lần cuối bởi",
|
||||
"previously_edited_by": "Trước đây được chỉnh sửa bởi",
|
||||
"edited_by": "Được chỉnh sửa bởi"
|
||||
},
|
||||
"self_hosted_maintenance_message": {
|
||||
"plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane không khởi động được. Điều này có thể do một hoặc nhiều dịch vụ Plane không khởi động được.",
|
||||
"choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Chọn View Logs từ setup.sh và log Docker để chắc chắn."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user