Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc97921195 |
+1
-2
@@ -2,7 +2,6 @@
|
||||
*.pyc
|
||||
.env
|
||||
venv
|
||||
.venv
|
||||
node_modules/
|
||||
**/node_modules/
|
||||
npm-debug.log
|
||||
@@ -15,4 +14,4 @@ build/
|
||||
out/
|
||||
**/out/
|
||||
dist/
|
||||
**/dist/
|
||||
**/dist/
|
||||
+1
-1
@@ -31,7 +31,7 @@
|
||||
"lucide-react": "^0.469.0",
|
||||
"mobx": "^6.12.0",
|
||||
"mobx-react": "^9.1.1",
|
||||
"next": "^14.2.29",
|
||||
"next": "^14.2.28",
|
||||
"next-themes": "^0.2.1",
|
||||
"postcss": "^8.4.38",
|
||||
"react": "^18.3.1",
|
||||
|
||||
@@ -5,6 +5,7 @@ from .cycle import urlpatterns as cycle_patterns
|
||||
from .module import urlpatterns as module_patterns
|
||||
from .intake import urlpatterns as intake_patterns
|
||||
from .member import urlpatterns as member_patterns
|
||||
from .workitem import urlpatterns as workitem_patterns
|
||||
|
||||
urlpatterns = [
|
||||
*project_patterns,
|
||||
@@ -14,4 +15,5 @@ urlpatterns = [
|
||||
*module_patterns,
|
||||
*intake_patterns,
|
||||
*member_patterns,
|
||||
*workitem_patterns,
|
||||
]
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
from django.urls import path
|
||||
|
||||
from plane.api.views import (
|
||||
IssueAPIEndpoint,
|
||||
LabelAPIEndpoint,
|
||||
IssueLinkAPIEndpoint,
|
||||
IssueCommentAPIEndpoint,
|
||||
IssueActivityAPIEndpoint,
|
||||
WorkspaceIssueAPIEndpoint,
|
||||
IssueAttachmentEndpoint,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/workitems/<str:project__identifier>-<str:issue__identifier>/",
|
||||
WorkspaceIssueAPIEndpoint.as_view(),
|
||||
name="workitem-by-identifier",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/workitems/",
|
||||
IssueAPIEndpoint.as_view(),
|
||||
name="workitem",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/workitems/<uuid:pk>/",
|
||||
IssueAPIEndpoint.as_view(),
|
||||
name="workitem",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/labels/",
|
||||
LabelAPIEndpoint.as_view(),
|
||||
name="workitem-label",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/labels/<uuid:pk>/",
|
||||
LabelAPIEndpoint.as_view(),
|
||||
name="workitem-label",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/workitems/<uuid:issue_id>/links/",
|
||||
IssueLinkAPIEndpoint.as_view(),
|
||||
name="workitem-link",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/workitems/<uuid:issue_id>/links/<uuid:pk>/",
|
||||
IssueLinkAPIEndpoint.as_view(),
|
||||
name="workitem-link",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/workitems/<uuid:issue_id>/comments/",
|
||||
IssueCommentAPIEndpoint.as_view(),
|
||||
name="workitem-comment",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/workitems/<uuid:issue_id>/comments/<uuid:pk>/",
|
||||
IssueCommentAPIEndpoint.as_view(),
|
||||
name="workitem-comment",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/workitems/<uuid:issue_id>/activities/",
|
||||
IssueActivityAPIEndpoint.as_view(),
|
||||
name="workitem-activity",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/workitems/<uuid:issue_id>/activities/<uuid:pk>/",
|
||||
IssueActivityAPIEndpoint.as_view(),
|
||||
name="workitem-activity",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/workitems/<uuid:issue_id>/issue-attachments/",
|
||||
IssueAttachmentEndpoint.as_view(),
|
||||
name="workitem-attachment",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/workitems/<uuid:issue_id>/issue-attachments/<uuid:pk>/",
|
||||
IssueAttachmentEndpoint.as_view(),
|
||||
name="workitem-attachment",
|
||||
),
|
||||
]
|
||||
@@ -148,13 +148,10 @@ class ProjectMemberAdminSerializer(BaseSerializer):
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
|
||||
original_role = serializers.IntegerField(source='role', read_only=True)
|
||||
|
||||
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
|
||||
class Meta:
|
||||
model = ProjectMember
|
||||
fields = ("id", "role", "member", "project", "original_role", "created_at")
|
||||
read_only_fields = ["original_role", "created_at"]
|
||||
fields = ("id", "role", "member", "project")
|
||||
|
||||
|
||||
class ProjectMemberInviteSerializer(BaseSerializer):
|
||||
|
||||
@@ -3,22 +3,11 @@ from rest_framework import serializers
|
||||
|
||||
# Module import
|
||||
from plane.db.models import Account, Profile, User, Workspace, WorkspaceMemberInvite
|
||||
from plane.utils.url import contains_url
|
||||
|
||||
from .base import BaseSerializer
|
||||
|
||||
|
||||
class UserSerializer(BaseSerializer):
|
||||
def validate_first_name(self, value):
|
||||
if contains_url(value):
|
||||
raise serializers.ValidationError("First name cannot contain a URL.")
|
||||
return value
|
||||
|
||||
def validate_last_name(self, value):
|
||||
if contains_url(value):
|
||||
raise serializers.ValidationError("Last name cannot contain a URL.")
|
||||
return value
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
# Exclude password field from the serializer
|
||||
@@ -110,16 +99,11 @@ class UserMeSettingsSerializer(BaseSerializer):
|
||||
workspace_member__member=obj.id,
|
||||
workspace_member__is_active=True,
|
||||
).first()
|
||||
logo_asset_url = workspace.logo_asset.asset_url if workspace.logo_asset is not None else ""
|
||||
return {
|
||||
"last_workspace_id": profile.last_workspace_id,
|
||||
"last_workspace_slug": (
|
||||
workspace.slug if workspace is not None else ""
|
||||
),
|
||||
"last_workspace_name": (
|
||||
workspace.name if workspace is not None else ""
|
||||
),
|
||||
"last_workspace_logo": (logo_asset_url),
|
||||
"fallback_workspace_id": profile.last_workspace_id,
|
||||
"fallback_workspace_slug": (
|
||||
workspace.slug if workspace is not None else ""
|
||||
|
||||
@@ -25,12 +25,10 @@ from plane.db.models import (
|
||||
WorkspaceUserPreference,
|
||||
)
|
||||
from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS
|
||||
from plane.utils.url import contains_url
|
||||
|
||||
# Django imports
|
||||
from django.core.validators import URLValidator
|
||||
from django.core.exceptions import ValidationError
|
||||
import re
|
||||
|
||||
|
||||
class WorkSpaceSerializer(DynamicBaseSerializer):
|
||||
@@ -38,21 +36,10 @@ class WorkSpaceSerializer(DynamicBaseSerializer):
|
||||
logo_url = serializers.CharField(read_only=True)
|
||||
role = serializers.IntegerField(read_only=True)
|
||||
|
||||
def validate_name(self, value):
|
||||
# Check if the name contains a URL
|
||||
if contains_url(value):
|
||||
raise serializers.ValidationError("Name must not contain URLs")
|
||||
return value
|
||||
|
||||
def validate_slug(self, value):
|
||||
# Check if the slug is restricted
|
||||
if value in RESTRICTED_WORKSPACE_SLUGS:
|
||||
raise serializers.ValidationError("Slug is not valid")
|
||||
# Slug should only contain alphanumeric characters, hyphens, and underscores
|
||||
if not re.match(r"^[a-zA-Z0-9_-]+$", value):
|
||||
raise serializers.ValidationError(
|
||||
"Slug can only contain letters, numbers, hyphens (-), and underscores (_)"
|
||||
)
|
||||
return value
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -12,7 +12,6 @@ from plane.app.views import (
|
||||
AssetRestoreEndpoint,
|
||||
ProjectAssetEndpoint,
|
||||
ProjectBulkAssetEndpoint,
|
||||
AssetCheckEndpoint,
|
||||
)
|
||||
|
||||
|
||||
@@ -82,11 +81,5 @@ urlpatterns = [
|
||||
path(
|
||||
"assets/v2/workspaces/<str:slug>/projects/<uuid:project_id>/<uuid:entity_id>/bulk/",
|
||||
ProjectBulkAssetEndpoint.as_view(),
|
||||
name="bulk-asset-update",
|
||||
),
|
||||
path(
|
||||
"assets/v2/workspaces/<str:slug>/check/<uuid:asset_id>/",
|
||||
AssetCheckEndpoint.as_view(),
|
||||
name="asset-check",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -106,7 +106,6 @@ from .asset.v2 import (
|
||||
AssetRestoreEndpoint,
|
||||
ProjectAssetEndpoint,
|
||||
ProjectBulkAssetEndpoint,
|
||||
AssetCheckEndpoint,
|
||||
)
|
||||
from .issue.base import (
|
||||
IssueListEndpoint,
|
||||
|
||||
@@ -707,14 +707,3 @@ class ProjectBulkAssetEndpoint(BaseAPIView):
|
||||
pass
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class AssetCheckEndpoint(BaseAPIView):
|
||||
"""Endpoint to check if an asset exists."""
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def get(self, request, slug, asset_id):
|
||||
asset = FileAsset.all_objects.filter(
|
||||
id=asset_id, workspace__slug=slug, deleted_at__isnull=True
|
||||
).exists()
|
||||
return Response({"exists": asset}, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -15,7 +15,6 @@ from plane.app.serializers import IssueLinkSerializer
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.db.models import IssueLink
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
from plane.bgtasks.work_item_link_task import crawl_work_item_link_title
|
||||
from plane.utils.host import base_host
|
||||
|
||||
|
||||
@@ -45,9 +44,6 @@ class IssueLinkViewSet(BaseViewSet):
|
||||
serializer = IssueLinkSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(project_id=project_id, issue_id=issue_id)
|
||||
crawl_work_item_link_title(
|
||||
serializer.data.get("id"), serializer.data.get("url")
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="link.activity.created",
|
||||
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
|
||||
@@ -59,10 +55,6 @@ class IssueLinkViewSet(BaseViewSet):
|
||||
notification=True,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
)
|
||||
|
||||
issue_link = self.get_queryset().get(id=serializer.data.get("id"))
|
||||
serializer = IssueLinkSerializer(issue_link)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@@ -74,14 +66,9 @@ class IssueLinkViewSet(BaseViewSet):
|
||||
current_instance = json.dumps(
|
||||
IssueLinkSerializer(issue_link).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
|
||||
serializer = IssueLinkSerializer(issue_link, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
crawl_work_item_link_title(
|
||||
serializer.data.get("id"), serializer.data.get("url")
|
||||
)
|
||||
|
||||
issue_activity.delay(
|
||||
type="link.activity.updated",
|
||||
requested_data=requested_data,
|
||||
@@ -93,9 +80,6 @@ class IssueLinkViewSet(BaseViewSet):
|
||||
notification=True,
|
||||
origin=base_host(request=request, is_app=True),
|
||||
)
|
||||
issue_link = self.get_queryset().get(id=serializer.data.get("id"))
|
||||
serializer = IssueLinkSerializer(issue_link)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@@ -168,8 +168,6 @@ 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(
|
||||
@@ -315,11 +313,7 @@ class UserProjectRolesEndpoint(BaseAPIView):
|
||||
|
||||
def get(self, request, slug):
|
||||
project_members = ProjectMember.objects.filter(
|
||||
workspace__slug=slug,
|
||||
member_id=request.user.id,
|
||||
is_active=True,
|
||||
member__member_workspace__workspace__slug=slug,
|
||||
member__member_workspace__is_active=True,
|
||||
workspace__slug=slug, member_id=request.user.id, is_active=True
|
||||
).values("project_id", "role")
|
||||
|
||||
project_members = {
|
||||
|
||||
@@ -3,7 +3,6 @@ import csv
|
||||
import io
|
||||
import os
|
||||
from datetime import date
|
||||
import uuid
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django.db import IntegrityError
|
||||
@@ -36,7 +35,6 @@ from plane.db.models import (
|
||||
Workspace,
|
||||
WorkspaceMember,
|
||||
WorkspaceTheme,
|
||||
Profile,
|
||||
)
|
||||
from plane.app.permissions import ROLE, allow_permission
|
||||
from django.utils.decorators import method_decorator
|
||||
@@ -45,7 +43,6 @@ from django.views.decorators.vary import vary_on_cookie
|
||||
from plane.utils.constants import RESTRICTED_WORKSPACE_SLUGS
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
from plane.bgtasks.workspace_seed_task import workspace_seed
|
||||
from plane.utils.url import contains_url
|
||||
|
||||
|
||||
class WorkSpaceViewSet(BaseViewSet):
|
||||
@@ -112,12 +109,6 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if contains_url(name):
|
||||
return Response(
|
||||
{"error": "Name cannot contain a URL"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if serializer.is_valid(raise_exception=True):
|
||||
serializer.save(owner=request.user)
|
||||
# Create Workspace member
|
||||
@@ -159,18 +150,8 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
|
||||
def remove_last_workspace_ids_from_user_settings(self, id: uuid.UUID) -> None:
|
||||
"""
|
||||
Remove the last workspace id from the user settings
|
||||
"""
|
||||
Profile.objects.filter(last_workspace_id=id).update(last_workspace_id=None)
|
||||
return
|
||||
|
||||
@allow_permission([ROLE.ADMIN], level="WORKSPACE")
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
# Get the workspace
|
||||
workspace = self.get_object()
|
||||
self.remove_last_workspace_ids_from_user_settings(workspace.id)
|
||||
return super().destroy(request, *args, **kwargs)
|
||||
|
||||
|
||||
@@ -178,6 +159,8 @@ class UserWorkSpacesEndpoint(BaseAPIView):
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["owner"]
|
||||
|
||||
@method_decorator(cache_control(private=True, max_age=12))
|
||||
@method_decorator(vary_on_cookie)
|
||||
def get(self, request):
|
||||
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||
member_count = (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# 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
|
||||
@@ -134,7 +133,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, updated_at=timezone.now())
|
||||
).update(is_active=False)
|
||||
|
||||
workspace_member.is_active = False
|
||||
workspace_member.save()
|
||||
@@ -195,7 +194,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, updated_at=timezone.now())
|
||||
).update(is_active=False)
|
||||
|
||||
# # Deactivate the user
|
||||
workspace_member.is_active = False
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
# Python imports
|
||||
import logging
|
||||
|
||||
|
||||
# Third party imports
|
||||
from celery import shared_task
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from urllib.parse import urlparse, urljoin
|
||||
import base64
|
||||
import ipaddress
|
||||
from typing import Dict, Any
|
||||
from typing import Optional
|
||||
from plane.db.models import IssueLink
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
||||
logger = logging.getLogger("plane.worker")
|
||||
|
||||
|
||||
DEFAULT_FAVICON = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWxpbmstaWNvbiBsdWNpZGUtbGluayI+PHBhdGggZD0iTTEwIDEzYTUgNSAwIDAgMCA3LjU0LjU0bDMtM2E1IDUgMCAwIDAtNy4wNy03LjA3bC0xLjcyIDEuNzEiLz48cGF0aCBkPSJNMTQgMTFhNSA1IDAgMCAwLTcuNTQtLjU0bC0zIDNhNSA1IDAgMCAwIDcuMDcgNy4wN2wxLjcxLTEuNzEiLz48L3N2Zz4=" # noqa: E501
|
||||
|
||||
|
||||
@shared_task
|
||||
def crawl_work_item_link_title(id: str, url: str) -> None:
|
||||
meta_data = crawl_work_item_link_title_and_favicon(url)
|
||||
issue_link = IssueLink.objects.get(id=id)
|
||||
|
||||
issue_link.metadata = meta_data
|
||||
|
||||
issue_link.save()
|
||||
|
||||
|
||||
def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Crawls a URL to extract the title and favicon.
|
||||
|
||||
Args:
|
||||
url (str): The URL to crawl
|
||||
|
||||
Returns:
|
||||
str: JSON string containing title and base64-encoded favicon
|
||||
"""
|
||||
try:
|
||||
# Prevent access to private IP ranges
|
||||
parsed = urlparse(url)
|
||||
|
||||
try:
|
||||
ip = ipaddress.ip_address(parsed.hostname)
|
||||
if ip.is_private or ip.is_loopback or ip.is_reserved:
|
||||
raise ValueError("Access to private/internal networks is not allowed")
|
||||
except ValueError:
|
||||
# Not an IP address, continue with domain validation
|
||||
pass
|
||||
|
||||
# Set up headers to mimic a real browser
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" # noqa: E501
|
||||
}
|
||||
|
||||
# Fetch the main page
|
||||
response = requests.get(url, headers=headers, timeout=2)
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
# Parse HTML
|
||||
soup = BeautifulSoup(response.content, "html.parser")
|
||||
|
||||
# Extract title
|
||||
title_tag = soup.find("title")
|
||||
title = title_tag.get_text().strip() if title_tag else None
|
||||
|
||||
# Fetch and encode favicon
|
||||
favicon_base64 = fetch_and_encode_favicon(headers, soup, url)
|
||||
|
||||
# Prepare result
|
||||
result = {
|
||||
"title": title,
|
||||
"favicon": favicon_base64["favicon_base64"],
|
||||
"url": url,
|
||||
"favicon_url": favicon_base64["favicon_url"],
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
except requests.RequestException as e:
|
||||
log_exception(e)
|
||||
return {
|
||||
"error": f"Request failed: {str(e)}",
|
||||
"title": None,
|
||||
"favicon": None,
|
||||
"url": url,
|
||||
}
|
||||
except Exception as e:
|
||||
log_exception(e)
|
||||
return {
|
||||
"error": f"Unexpected error: {str(e)}",
|
||||
"title": None,
|
||||
"favicon": None,
|
||||
"url": url,
|
||||
}
|
||||
|
||||
|
||||
def find_favicon_url(soup: BeautifulSoup, base_url: str) -> Optional[str]:
|
||||
"""
|
||||
Find the favicon URL from HTML soup.
|
||||
|
||||
Args:
|
||||
soup: BeautifulSoup object
|
||||
base_url: Base URL for resolving relative paths
|
||||
|
||||
Returns:
|
||||
str: Absolute URL to favicon or None
|
||||
"""
|
||||
# Look for various favicon link tags
|
||||
favicon_selectors = [
|
||||
'link[rel="icon"]',
|
||||
'link[rel="shortcut icon"]',
|
||||
'link[rel="apple-touch-icon"]',
|
||||
'link[rel="apple-touch-icon-precomposed"]',
|
||||
]
|
||||
|
||||
for selector in favicon_selectors:
|
||||
favicon_tag = soup.select_one(selector)
|
||||
if favicon_tag and favicon_tag.get("href"):
|
||||
return urljoin(base_url, favicon_tag["href"])
|
||||
|
||||
# Fallback to /favicon.ico
|
||||
parsed_url = urlparse(base_url)
|
||||
fallback_url = f"{parsed_url.scheme}://{parsed_url.netloc}/favicon.ico"
|
||||
|
||||
# Check if fallback exists
|
||||
try:
|
||||
response = requests.head(fallback_url, timeout=2)
|
||||
response.raise_for_status()
|
||||
if response.status_code == 200:
|
||||
return fallback_url
|
||||
except requests.RequestException as e:
|
||||
log_exception(e)
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def fetch_and_encode_favicon(
|
||||
headers: Dict[str, str], soup: BeautifulSoup, url: str
|
||||
) -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
Fetch favicon and encode it as base64.
|
||||
|
||||
Args:
|
||||
favicon_url: URL to the favicon
|
||||
headers: Request headers
|
||||
|
||||
Returns:
|
||||
str: Base64 encoded favicon with data URI prefix or None
|
||||
"""
|
||||
try:
|
||||
favicon_url = find_favicon_url(soup, url)
|
||||
if favicon_url is None:
|
||||
return {
|
||||
"favicon_url": None,
|
||||
"favicon_base64": f"data:image/svg+xml;base64,{DEFAULT_FAVICON}",
|
||||
}
|
||||
|
||||
response = requests.get(favicon_url, headers=headers, timeout=2)
|
||||
response.raise_for_status()
|
||||
|
||||
# Get content type
|
||||
content_type = response.headers.get("content-type", "image/x-icon")
|
||||
|
||||
# Convert to base64
|
||||
favicon_base64 = base64.b64encode(response.content).decode("utf-8")
|
||||
|
||||
# Return as data URI
|
||||
return {
|
||||
"favicon_url": favicon_url,
|
||||
"favicon_base64": f"data:{content_type};base64,{favicon_base64}",
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch favicon: {e}")
|
||||
return {
|
||||
"favicon_url": None,
|
||||
"favicon_base64": f"data:image/svg+xml;base64,{DEFAULT_FAVICON}",
|
||||
}
|
||||
@@ -4,14 +4,6 @@ from typing import Optional
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
|
||||
def contains_url(value: str) -> bool:
|
||||
"""
|
||||
Check if the value contains a URL.
|
||||
"""
|
||||
url_pattern = re.compile(r"https?://|www\\.")
|
||||
return bool(url_pattern.search(value))
|
||||
|
||||
|
||||
def is_valid_url(url: str) -> bool:
|
||||
"""
|
||||
Validates whether the given string is a well-formed URL.
|
||||
|
||||
@@ -32,6 +32,5 @@ export * from "./dashboard";
|
||||
export * from "./page";
|
||||
export * from "./emoji";
|
||||
export * from "./subscription";
|
||||
export * from "./settings";
|
||||
export * from "./icon";
|
||||
export * from "./analytics-v2";
|
||||
|
||||
@@ -1,53 +1,39 @@
|
||||
export const PROFILE_SETTINGS = {
|
||||
profile: {
|
||||
key: "profile",
|
||||
i18n_label: "profile.actions.profile",
|
||||
href: `/settings/account`,
|
||||
highlight: (pathname: string) => pathname === "/settings/account/",
|
||||
},
|
||||
security: {
|
||||
key: "security",
|
||||
i18n_label: "profile.actions.security",
|
||||
href: `/settings/account/security`,
|
||||
highlight: (pathname: string) => pathname === "/settings/account/security/",
|
||||
},
|
||||
activity: {
|
||||
key: "activity",
|
||||
i18n_label: "profile.actions.activity",
|
||||
href: `/settings/account/activity`,
|
||||
highlight: (pathname: string) => pathname === "/settings/account/activity/",
|
||||
},
|
||||
preferences: {
|
||||
key: "preferences",
|
||||
i18n_label: "profile.actions.preferences",
|
||||
href: `/settings/account/preferences`,
|
||||
highlight: (pathname: string) => pathname === "/settings/account/preferences",
|
||||
},
|
||||
notifications: {
|
||||
key: "notifications",
|
||||
i18n_label: "profile.actions.notifications",
|
||||
href: `/settings/account/notifications`,
|
||||
highlight: (pathname: string) => pathname === "/settings/account/notifications/",
|
||||
},
|
||||
"api-tokens": {
|
||||
key: "api-tokens",
|
||||
i18n_label: "profile.actions.api-tokens",
|
||||
href: `/settings/account/api-tokens`,
|
||||
highlight: (pathname: string) => pathname === "/settings/account/api-tokens/",
|
||||
},
|
||||
};
|
||||
export const PROFILE_ACTION_LINKS: {
|
||||
key: string;
|
||||
i18n_label: string;
|
||||
href: string;
|
||||
highlight: (pathname: string) => boolean;
|
||||
}[] = [
|
||||
PROFILE_SETTINGS["profile"],
|
||||
PROFILE_SETTINGS["security"],
|
||||
PROFILE_SETTINGS["activity"],
|
||||
PROFILE_SETTINGS["preferences"],
|
||||
PROFILE_SETTINGS["notifications"],
|
||||
PROFILE_SETTINGS["api-tokens"],
|
||||
{
|
||||
key: "profile",
|
||||
i18n_label: "profile.actions.profile",
|
||||
href: `/profile`,
|
||||
highlight: (pathname: string) => pathname === "/profile/",
|
||||
},
|
||||
{
|
||||
key: "security",
|
||||
i18n_label: "profile.actions.security",
|
||||
href: `/profile/security`,
|
||||
highlight: (pathname: string) => pathname === "/profile/security/",
|
||||
},
|
||||
{
|
||||
key: "activity",
|
||||
i18n_label: "profile.actions.activity",
|
||||
href: `/profile/activity`,
|
||||
highlight: (pathname: string) => pathname === "/profile/activity/",
|
||||
},
|
||||
{
|
||||
key: "appearance",
|
||||
i18n_label: "profile.actions.appearance",
|
||||
href: `/profile/appearance`,
|
||||
highlight: (pathname: string) => pathname.includes("/profile/appearance"),
|
||||
},
|
||||
{
|
||||
key: "notifications",
|
||||
i18n_label: "profile.actions.notifications",
|
||||
href: `/profile/notifications`,
|
||||
highlight: (pathname: string) => pathname === "/profile/notifications/",
|
||||
},
|
||||
];
|
||||
|
||||
export const PROFILE_VIEWER_TAB = [
|
||||
@@ -86,23 +72,6 @@ export const PROFILE_ADMINS_TAB = [
|
||||
},
|
||||
];
|
||||
|
||||
export const PREFERENCE_OPTIONS: {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}[] = [
|
||||
{
|
||||
id: "theme",
|
||||
title: "theme",
|
||||
description: "select_or_customize_your_interface_color_scheme",
|
||||
},
|
||||
{
|
||||
id: "start_of_week",
|
||||
title: "First day of the week",
|
||||
description: "This will change how all calendars in your app look.",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @description The start of the week for the user
|
||||
* @enum {number}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { PROFILE_SETTINGS } from ".";
|
||||
import { WORKSPACE_SETTINGS } from "./workspace";
|
||||
|
||||
export enum WORKSPACE_SETTINGS_CATEGORY {
|
||||
ADMINISTRATION = "administration",
|
||||
FEATURES = "features",
|
||||
DEVELOPER = "developer",
|
||||
}
|
||||
|
||||
export enum PROFILE_SETTINGS_CATEGORY {
|
||||
YOUR_PROFILE = "your profile",
|
||||
DEVELOPER = "developer",
|
||||
}
|
||||
|
||||
export enum PROJECT_SETTINGS_CATEGORY {
|
||||
PROJECTS = "projects",
|
||||
}
|
||||
|
||||
export const WORKSPACE_SETTINGS_CATEGORIES = [
|
||||
WORKSPACE_SETTINGS_CATEGORY.ADMINISTRATION,
|
||||
WORKSPACE_SETTINGS_CATEGORY.FEATURES,
|
||||
WORKSPACE_SETTINGS_CATEGORY.DEVELOPER,
|
||||
];
|
||||
|
||||
export const PROFILE_SETTINGS_CATEGORIES = [
|
||||
PROFILE_SETTINGS_CATEGORY.YOUR_PROFILE,
|
||||
PROFILE_SETTINGS_CATEGORY.DEVELOPER,
|
||||
];
|
||||
|
||||
export const PROJECT_SETTINGS_CATEGORIES = [PROJECT_SETTINGS_CATEGORY.PROJECTS];
|
||||
|
||||
export const GROUPED_WORKSPACE_SETTINGS = {
|
||||
[WORKSPACE_SETTINGS_CATEGORY.ADMINISTRATION]: [
|
||||
WORKSPACE_SETTINGS["general"],
|
||||
WORKSPACE_SETTINGS["members"],
|
||||
WORKSPACE_SETTINGS["billing-and-plans"],
|
||||
WORKSPACE_SETTINGS["export"],
|
||||
],
|
||||
[WORKSPACE_SETTINGS_CATEGORY.FEATURES]: [],
|
||||
[WORKSPACE_SETTINGS_CATEGORY.DEVELOPER]: [WORKSPACE_SETTINGS["webhooks"]],
|
||||
};
|
||||
|
||||
export const GROUPED_PROFILE_SETTINGS = {
|
||||
[PROFILE_SETTINGS_CATEGORY.YOUR_PROFILE]: [
|
||||
PROFILE_SETTINGS["profile"],
|
||||
PROFILE_SETTINGS["preferences"],
|
||||
PROFILE_SETTINGS["notifications"],
|
||||
PROFILE_SETTINGS["security"],
|
||||
PROFILE_SETTINGS["activity"],
|
||||
],
|
||||
[PROFILE_SETTINGS_CATEGORY.DEVELOPER]: [PROFILE_SETTINGS["api-tokens"]],
|
||||
};
|
||||
@@ -114,6 +114,13 @@ export const WORKSPACE_SETTINGS = {
|
||||
access: [EUserWorkspaceRoles.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/webhooks/`,
|
||||
},
|
||||
"api-tokens": {
|
||||
key: "api-tokens",
|
||||
i18n_label: "workspace_settings.settings.api_tokens.title",
|
||||
href: `/settings/api-tokens`,
|
||||
access: [EUserWorkspaceRoles.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/api-tokens/`,
|
||||
},
|
||||
};
|
||||
|
||||
export const WORKSPACE_SETTINGS_ACCESS = Object.fromEntries(
|
||||
@@ -132,6 +139,7 @@ export const WORKSPACE_SETTINGS_LINKS: {
|
||||
WORKSPACE_SETTINGS["billing-and-plans"],
|
||||
WORKSPACE_SETTINGS["export"],
|
||||
WORKSPACE_SETTINGS["webhooks"],
|
||||
WORKSPACE_SETTINGS["api-tokens"],
|
||||
];
|
||||
|
||||
export const ROLE = {
|
||||
|
||||
@@ -2,31 +2,30 @@ import { HocuspocusProvider } from "@hocuspocus/provider";
|
||||
import { AnyExtension } from "@tiptap/core";
|
||||
import { SlashCommands } from "@/extensions";
|
||||
// plane editor types
|
||||
import { TEmbedConfig } from "@/plane-editor/types";
|
||||
import { TIssueEmbedConfig } from "@/plane-editor/types";
|
||||
// types
|
||||
import { TExtensions, TFileHandler, TUserDetails } from "@/types";
|
||||
import { TExtensions, TUserDetails } from "@/types";
|
||||
|
||||
export type TDocumentEditorAdditionalExtensionsProps = {
|
||||
disabledExtensions: TExtensions[];
|
||||
embedConfig: TEmbedConfig | undefined;
|
||||
fileHandler: TFileHandler;
|
||||
provider?: HocuspocusProvider;
|
||||
type Props = {
|
||||
disabledExtensions?: TExtensions[];
|
||||
issueEmbedConfig: TIssueEmbedConfig | undefined;
|
||||
provider: HocuspocusProvider;
|
||||
userDetails: TUserDetails;
|
||||
};
|
||||
|
||||
export type TDocumentEditorAdditionalExtensionsRegistry = {
|
||||
type ExtensionConfig = {
|
||||
isEnabled: (disabledExtensions: TExtensions[]) => boolean;
|
||||
getExtension: (props: TDocumentEditorAdditionalExtensionsProps) => AnyExtension;
|
||||
getExtension: (props: Props) => AnyExtension;
|
||||
};
|
||||
|
||||
const extensionRegistry: TDocumentEditorAdditionalExtensionsRegistry[] = [
|
||||
const extensionRegistry: ExtensionConfig[] = [
|
||||
{
|
||||
isEnabled: (disabledExtensions) => !disabledExtensions.includes("slash-commands"),
|
||||
getExtension: ({ disabledExtensions }) => SlashCommands({ disabledExtensions }),
|
||||
getExtension: () => SlashCommands({}),
|
||||
},
|
||||
];
|
||||
|
||||
export const DocumentEditorAdditionalExtensions = (_props: TDocumentEditorAdditionalExtensionsProps) => {
|
||||
export const DocumentEditorAdditionalExtensions = (_props: Props) => {
|
||||
const { disabledExtensions = [] } = _props;
|
||||
|
||||
const documentExtensions = extensionRegistry
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
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;
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
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,7 +15,6 @@ export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => {
|
||||
disabledExtensions,
|
||||
displayConfig = DEFAULT_DISPLAY_CONFIG,
|
||||
editorClassName = "",
|
||||
extensions,
|
||||
fileHandler,
|
||||
forwardedRef,
|
||||
id,
|
||||
@@ -26,7 +25,6 @@ export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => {
|
||||
const editor = useReadOnlyEditor({
|
||||
disabledExtensions,
|
||||
editorClassName,
|
||||
extensions,
|
||||
fileHandler,
|
||||
forwardedRef,
|
||||
initialValue,
|
||||
|
||||
@@ -3,20 +3,12 @@ import { forwardRef, useCallback } from "react";
|
||||
import { EditorWrapper } from "@/components/editors";
|
||||
import { EditorBubbleMenu } from "@/components/menus";
|
||||
// extensions
|
||||
import { SideMenuExtension } from "@/extensions";
|
||||
// plane editor imports
|
||||
import { RichTextEditorAdditionalExtensions } from "@/plane-editor/extensions/rich-text/extensions";
|
||||
import { SideMenuExtension, SlashCommands } from "@/extensions";
|
||||
// types
|
||||
import { EditorRefApi, IRichTextEditor } from "@/types";
|
||||
|
||||
const RichTextEditor = (props: IRichTextEditor) => {
|
||||
const {
|
||||
disabledExtensions,
|
||||
dragDropEnabled,
|
||||
fileHandler,
|
||||
bubbleMenuEnabled = true,
|
||||
extensions: externalExtensions = [],
|
||||
} = props;
|
||||
const { disabledExtensions, dragDropEnabled, bubbleMenuEnabled = true, extensions: externalExtensions = [] } = props;
|
||||
|
||||
const getExtensions = useCallback(() => {
|
||||
const extensions = [
|
||||
@@ -25,14 +17,17 @@ 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, fileHandler]);
|
||||
}, [dragDropEnabled, disabledExtensions, externalExtensions]);
|
||||
|
||||
return (
|
||||
<EditorWrapper {...props} extensions={getExtensions()}>
|
||||
|
||||
@@ -1,33 +1,11 @@
|
||||
import { forwardRef, useCallback } from "react";
|
||||
// plane editor extensions
|
||||
import { RichTextReadOnlyEditorAdditionalExtensions } from "@/plane-editor/extensions/rich-text/read-only-extensions";
|
||||
import { forwardRef } from "react";
|
||||
// types
|
||||
import { EditorReadOnlyRefApi, IRichTextReadOnlyEditor } from "@/types";
|
||||
// local imports
|
||||
import { ReadOnlyEditorWrapper } from "../read-only-editor-wrapper";
|
||||
|
||||
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>}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const RichTextReadOnlyEditorWithRef = forwardRef<EditorReadOnlyRefApi, IRichTextReadOnlyEditor>((props, ref) => (
|
||||
<ReadOnlyEditorWrapper {...props} forwardedRef={ref as React.MutableRefObject<EditorReadOnlyRefApi | null>} />
|
||||
));
|
||||
|
||||
RichTextReadOnlyEditorWithRef.displayName = "RichReadOnlyEditorWithRef";
|
||||
|
||||
|
||||
@@ -86,10 +86,6 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
|
||||
[editor]
|
||||
);
|
||||
|
||||
const handleInvalidFile = useCallback((_error: EFileError, _file: File, message: string) => {
|
||||
alert(message);
|
||||
}, []);
|
||||
|
||||
// hooks
|
||||
const { isUploading: isImageBeingUploaded, uploadFile } = useUploader({
|
||||
acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES,
|
||||
@@ -98,12 +94,18 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
|
||||
handleProgressStatus,
|
||||
loadFileFromFileSystem: loadImageFromFileSystem,
|
||||
maxFileSize,
|
||||
onInvalidFile: handleInvalidFile,
|
||||
onUpload,
|
||||
});
|
||||
|
||||
const handleInvalidFile = useCallback((_error: EFileError, message: string) => {
|
||||
alert(message);
|
||||
}, []);
|
||||
|
||||
const { draggedInside, onDrop, onDragEnter, onDragLeave } = useDropZone({
|
||||
acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES,
|
||||
editor,
|
||||
maxFileSize,
|
||||
onInvalidFile: handleInvalidFile,
|
||||
pos: getPos(),
|
||||
type: "image",
|
||||
uploader: uploadFile,
|
||||
@@ -138,8 +140,11 @@ export const CustomImageUploader = (props: CustomImageUploaderProps) => {
|
||||
return;
|
||||
}
|
||||
await uploadFirstFileAndInsertRemaining({
|
||||
acceptedMimeTypes: ACCEPTED_IMAGE_MIME_TYPES,
|
||||
editor,
|
||||
filesList,
|
||||
maxFileSize,
|
||||
onInvalidFile: (_error, message) => alert(message),
|
||||
pos: getPos(),
|
||||
type: "image",
|
||||
uploader: uploadFile,
|
||||
|
||||
@@ -170,9 +170,8 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
|
||||
CustomTextAlignExtension,
|
||||
CustomCalloutExtension,
|
||||
UtilityExtension({
|
||||
disabledExtensions,
|
||||
fileHandler,
|
||||
isEditable: editable,
|
||||
fileHandler,
|
||||
}),
|
||||
CustomColorExtension,
|
||||
...CoreEditorAdditionalExtensions({
|
||||
|
||||
@@ -127,9 +127,8 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => {
|
||||
CustomTextAlignExtension,
|
||||
CustomCalloutReadOnlyExtension,
|
||||
UtilityExtension({
|
||||
disabledExtensions,
|
||||
fileHandler,
|
||||
isEditable: false,
|
||||
fileHandler,
|
||||
}),
|
||||
...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 { TExtensions, TFileHandler, TReadOnlyFileHandler } from "@/types";
|
||||
import { TFileHandler, TReadOnlyFileHandler } from "@/types";
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
interface Commands {
|
||||
@@ -24,14 +24,13 @@ export interface UtilityExtensionStorage {
|
||||
}
|
||||
|
||||
type Props = {
|
||||
disabledExtensions: TExtensions[];
|
||||
fileHandler: TFileHandler | TReadOnlyFileHandler;
|
||||
isEditable: boolean;
|
||||
};
|
||||
|
||||
export const UtilityExtension = (props: Props) => {
|
||||
const { disabledExtensions, fileHandler, isEditable } = props;
|
||||
const { restore } = fileHandler;
|
||||
const { fileHandler, isEditable } = props;
|
||||
const { restore: restoreImageFn } = fileHandler;
|
||||
|
||||
return Extension.create<Record<string, unknown>, UtilityExtensionStorage>({
|
||||
name: "utility",
|
||||
@@ -46,15 +45,12 @@ export const UtilityExtension = (props: Props) => {
|
||||
}),
|
||||
...codemark({ markType: this.editor.schema.marks.code }),
|
||||
MarkdownClipboardPlugin(this.editor),
|
||||
DropHandlerPlugin({
|
||||
disabledExtensions,
|
||||
editor: this.editor,
|
||||
}),
|
||||
DropHandlerPlugin(this.editor),
|
||||
];
|
||||
},
|
||||
|
||||
onCreate() {
|
||||
restorePublicImages(this.editor, restore);
|
||||
restorePublicImages(this.editor, restoreImageFn);
|
||||
},
|
||||
|
||||
addStorage() {
|
||||
|
||||
@@ -92,8 +92,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
|
||||
...(extensions ?? []),
|
||||
...DocumentEditorAdditionalExtensions({
|
||||
disabledExtensions,
|
||||
embedConfig: embedHandler,
|
||||
fileHandler,
|
||||
issueEmbedConfig: embedHandler?.issue,
|
||||
provider,
|
||||
userDetails: user,
|
||||
}),
|
||||
|
||||
@@ -81,7 +81,6 @@ export const useEditor = (props: CustomEditorProps) => {
|
||||
immediatelyRender: false,
|
||||
shouldRerenderOnTransaction: false,
|
||||
autofocus,
|
||||
parseOptions: { preserveWhitespace: true },
|
||||
editorProps: {
|
||||
...CoreEditorProps({
|
||||
editorClassName,
|
||||
@@ -120,7 +119,7 @@ export const useEditor = (props: CustomEditorProps) => {
|
||||
const isUploadInProgress = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.uploadInProgress;
|
||||
if (!editor.isDestroyed && !isUploadInProgress) {
|
||||
try {
|
||||
editor.commands.setContent(value, false, { preserveWhitespace: true });
|
||||
editor.commands.setContent(value, false, { preserveWhitespace: "full" });
|
||||
if (editor.state.selection) {
|
||||
const docLength = editor.state.doc.content.size;
|
||||
const relativePosition = Math.min(editor.state.selection.from, docLength - 1);
|
||||
@@ -154,7 +153,7 @@ export const useEditor = (props: CustomEditorProps) => {
|
||||
editor?.chain().setMeta(CORE_EDITOR_META.SKIP_FILE_DELETION, true).clearContent(emitUpdate).run();
|
||||
},
|
||||
setEditorValue: (content: string, emitUpdate = false) => {
|
||||
editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: true });
|
||||
editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: "full" });
|
||||
},
|
||||
setEditorValueAtCursorPosition: (content: string) => {
|
||||
if (editor?.state.selection) {
|
||||
|
||||
@@ -9,11 +9,11 @@ import { TEditorCommands } from "@/types";
|
||||
|
||||
type TUploaderArgs = {
|
||||
acceptedMimeTypes: string[];
|
||||
editorCommand: (file: File) => Promise<string | undefined>;
|
||||
editorCommand: (file: File) => Promise<string>;
|
||||
handleProgressStatus?: (isUploading: boolean) => void;
|
||||
loadFileFromFileSystem?: (file: string) => void;
|
||||
maxFileSize: number;
|
||||
onInvalidFile: (error: EFileError, file: File, message: string) => void;
|
||||
onInvalidFile: (error: EFileError, message: string) => void;
|
||||
onUpload: (url: string, file: File) => void;
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ export const useUploader = (args: TUploaderArgs) => {
|
||||
acceptedMimeTypes,
|
||||
file,
|
||||
maxFileSize,
|
||||
onError: (error, message) => onInvalidFile(error, file, message),
|
||||
onError: onInvalidFile,
|
||||
});
|
||||
if (!isValid) {
|
||||
handleProgressStatus?.(false);
|
||||
@@ -60,7 +60,7 @@ export const useUploader = (args: TUploaderArgs) => {
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
const url = await editorCommand(file);
|
||||
const url: string = await editorCommand(file);
|
||||
|
||||
if (!url) {
|
||||
throw new Error("Something went wrong while uploading the file.");
|
||||
@@ -89,14 +89,17 @@ export const useUploader = (args: TUploaderArgs) => {
|
||||
};
|
||||
|
||||
type TDropzoneArgs = {
|
||||
acceptedMimeTypes: string[];
|
||||
editor: Editor;
|
||||
maxFileSize: number;
|
||||
onInvalidFile: (error: EFileError, message: string) => void;
|
||||
pos: number;
|
||||
type: Extract<TEditorCommands, "attachment" | "image">;
|
||||
uploader: (file: File) => Promise<void>;
|
||||
};
|
||||
|
||||
export const useDropZone = (args: TDropzoneArgs) => {
|
||||
const { editor, pos, type, uploader } = args;
|
||||
const { acceptedMimeTypes, editor, maxFileSize, onInvalidFile, pos, type, uploader } = args;
|
||||
// states
|
||||
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||
const [draggedInside, setDraggedInside] = useState<boolean>(false);
|
||||
@@ -123,21 +126,22 @@ export const useDropZone = (args: TDropzoneArgs) => {
|
||||
async (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setDraggedInside(false);
|
||||
const filesList = e.dataTransfer.files;
|
||||
|
||||
if (filesList.length === 0 || !editor.isEditable) {
|
||||
if (e.dataTransfer.files.length === 0 || !editor.isEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filesList = e.dataTransfer.files;
|
||||
await uploadFirstFileAndInsertRemaining({
|
||||
acceptedMimeTypes,
|
||||
editor,
|
||||
filesList,
|
||||
maxFileSize,
|
||||
onInvalidFile,
|
||||
pos,
|
||||
type,
|
||||
uploader,
|
||||
});
|
||||
},
|
||||
[editor, pos, type, uploader]
|
||||
[acceptedMimeTypes, editor, maxFileSize, onInvalidFile, pos, type, uploader]
|
||||
);
|
||||
const onDragEnter = useCallback(() => setDraggedInside(true), []);
|
||||
const onDragLeave = useCallback(() => setDraggedInside(false), []);
|
||||
@@ -152,8 +156,11 @@ export const useDropZone = (args: TDropzoneArgs) => {
|
||||
};
|
||||
|
||||
type TMultipleFileArgs = {
|
||||
acceptedMimeTypes: string[];
|
||||
editor: Editor;
|
||||
filesList: FileList;
|
||||
maxFileSize: number;
|
||||
onInvalidFile: (error: EFileError, message: string) => void;
|
||||
pos: number;
|
||||
type: Extract<TEditorCommands, "attachment" | "image">;
|
||||
uploader: (file: File) => Promise<void>;
|
||||
@@ -161,18 +168,35 @@ type TMultipleFileArgs = {
|
||||
|
||||
// Upload the first file and insert the remaining ones for uploading multiple files
|
||||
export const uploadFirstFileAndInsertRemaining = async (args: TMultipleFileArgs) => {
|
||||
const { editor, filesList, pos, type, uploader } = args;
|
||||
const filesArray = Array.from(filesList);
|
||||
if (filesArray.length === 0) {
|
||||
const { acceptedMimeTypes, editor, filesList, maxFileSize, onInvalidFile, pos, type, uploader } = args;
|
||||
const filteredFiles: File[] = [];
|
||||
for (let i = 0; i < filesList.length; i += 1) {
|
||||
const file = filesList.item(i);
|
||||
if (
|
||||
file &&
|
||||
isFileValid({
|
||||
acceptedMimeTypes,
|
||||
file,
|
||||
maxFileSize,
|
||||
onError: onInvalidFile,
|
||||
})
|
||||
) {
|
||||
filteredFiles.push(file);
|
||||
}
|
||||
}
|
||||
if (filteredFiles.length !== filesList.length) {
|
||||
console.warn("Some files were invalid and have been ignored.");
|
||||
}
|
||||
if (filteredFiles.length === 0) {
|
||||
console.error("No files found to upload.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload the first file
|
||||
const firstFile = filesArray[0];
|
||||
const firstFile = filteredFiles[0];
|
||||
uploader(firstFile);
|
||||
// Insert the remaining files
|
||||
const remainingFiles = filesArray.slice(1);
|
||||
const remainingFiles = filteredFiles.slice(1);
|
||||
if (remainingFiles.length > 0) {
|
||||
const docSize = editor.state.doc.content.size;
|
||||
const posOfNextFileToBeInserted = Math.min(pos + 1, docSize);
|
||||
|
||||
@@ -46,7 +46,6 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
|
||||
immediatelyRender: true,
|
||||
shouldRerenderOnTransaction: false,
|
||||
content: typeof initialValue === "string" && initialValue.trim() !== "" ? initialValue : "<p></p>",
|
||||
parseOptions: { preserveWhitespace: true },
|
||||
editorProps: {
|
||||
...CoreReadOnlyEditorProps({
|
||||
editorClassName,
|
||||
@@ -72,7 +71,7 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
|
||||
// for syncing swr data on tab refocus etc
|
||||
useEffect(() => {
|
||||
if (initialValue === null || initialValue === undefined) return;
|
||||
if (editor && !editor.isDestroyed) editor?.commands.setContent(initialValue, false, { preserveWhitespace: true });
|
||||
if (editor && !editor.isDestroyed) editor?.commands.setContent(initialValue, false, { preserveWhitespace: "full" });
|
||||
}, [editor, initialValue]);
|
||||
|
||||
useImperativeHandle(forwardedRef, () => ({
|
||||
@@ -80,7 +79,7 @@ export const useReadOnlyEditor = (props: CustomReadOnlyEditorProps) => {
|
||||
editor?.chain().setMeta(CORE_EDITOR_META.SKIP_FILE_DELETION, true).clearContent(emitUpdate).run();
|
||||
},
|
||||
setEditorValue: (content: string, emitUpdate = false) => {
|
||||
editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: true });
|
||||
editor?.commands.setContent(content, emitUpdate, { preserveWhitespace: "full" });
|
||||
},
|
||||
getMarkDown: (): string => {
|
||||
const markdownOutput = editor?.storage.markdown.getMarkdown();
|
||||
|
||||
@@ -3,17 +3,10 @@ import { Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
// constants
|
||||
import { ACCEPTED_ATTACHMENT_MIME_TYPES, ACCEPTED_IMAGE_MIME_TYPES } from "@/constants/config";
|
||||
// types
|
||||
import { TEditorCommands, TExtensions } from "@/types";
|
||||
import { TEditorCommands } from "@/types";
|
||||
|
||||
type Props = {
|
||||
disabledExtensions?: TExtensions[];
|
||||
editor: Editor;
|
||||
};
|
||||
|
||||
export const DropHandlerPlugin = (props: Props): Plugin => {
|
||||
const { disabledExtensions, editor } = props;
|
||||
|
||||
return new Plugin({
|
||||
export const DropHandlerPlugin = (editor: Editor): Plugin =>
|
||||
new Plugin({
|
||||
key: new PluginKey("drop-handler-plugin"),
|
||||
props: {
|
||||
handlePaste: (view, event) => {
|
||||
@@ -32,7 +25,6 @@ export const DropHandlerPlugin = (props: Props): Plugin => {
|
||||
if (acceptedFiles.length) {
|
||||
const pos = view.state.selection.from;
|
||||
insertFilesSafely({
|
||||
disabledExtensions,
|
||||
editor,
|
||||
files: acceptedFiles,
|
||||
initialPos: pos,
|
||||
@@ -66,7 +58,6 @@ export const DropHandlerPlugin = (props: Props): Plugin => {
|
||||
if (coordinates) {
|
||||
const pos = coordinates.pos;
|
||||
insertFilesSafely({
|
||||
disabledExtensions,
|
||||
editor,
|
||||
files: acceptedFiles,
|
||||
initialPos: pos,
|
||||
@@ -80,10 +71,8 @@ export const DropHandlerPlugin = (props: Props): Plugin => {
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
type InsertFilesSafelyArgs = {
|
||||
disabledExtensions?: TExtensions[];
|
||||
editor: Editor;
|
||||
event: "insert" | "drop";
|
||||
files: File[];
|
||||
@@ -92,7 +81,7 @@ type InsertFilesSafelyArgs = {
|
||||
};
|
||||
|
||||
export const insertFilesSafely = async (args: InsertFilesSafelyArgs) => {
|
||||
const { disabledExtensions, editor, event, files, initialPos, type } = args;
|
||||
const { editor, event, files, initialPos, type } = args;
|
||||
let pos = initialPos;
|
||||
|
||||
for (const file of files) {
|
||||
@@ -111,7 +100,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" && !disabledExtensions?.includes("image")) {
|
||||
if (fileType === "image") {
|
||||
editor.commands.insertImageComponent({
|
||||
file,
|
||||
pos,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Editor } from "@tiptap/core";
|
||||
import { EditorState, Plugin, PluginKey, Transaction } from "@tiptap/pm/state";
|
||||
// constants
|
||||
import { CORE_EDITOR_META } from "@/constants/meta";
|
||||
// plane editor imports
|
||||
import { NODE_FILE_MAP } from "@/plane-editor/constants/utility";
|
||||
// types
|
||||
@@ -34,7 +32,7 @@ export const TrackFileDeletionPlugin = (editor: Editor, deleteHandler: TFileHand
|
||||
|
||||
transactions.forEach((transaction) => {
|
||||
// if the transaction has meta of skipFileDeletion set to true, then return (like while clearing the editor content programmatically)
|
||||
if (transaction.getMeta(CORE_EDITOR_META.SKIP_FILE_DELETION)) return;
|
||||
if (transaction.getMeta("skipFileDeletion")) return;
|
||||
|
||||
const removedFiles: TFileNode[] = [];
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export type TReadOnlyFileHandler = {
|
||||
checkIfAssetExists: (assetId: string) => Promise<boolean>;
|
||||
getAssetSrc: (path: string) => Promise<string>;
|
||||
restore: (assetSrc: string) => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -160,7 +160,6 @@ export interface IReadOnlyEditorProps {
|
||||
disabledExtensions: TExtensions[];
|
||||
displayConfig?: TDisplayConfig;
|
||||
editorClassName?: string;
|
||||
extensions?: Extensions;
|
||||
fileHandler: TReadOnlyFileHandler;
|
||||
forwardedRef?: React.MutableRefObject<EditorReadOnlyRefApi | null>;
|
||||
id: string;
|
||||
|
||||
@@ -31,7 +31,6 @@ export const SUPPORTED_LANGUAGES: ILanguageOption[] = [
|
||||
export enum ETranslationFiles {
|
||||
TRANSLATIONS = "translations",
|
||||
ACCESSIBILITY = "accessibility",
|
||||
EDITOR = "editor",
|
||||
}
|
||||
|
||||
export const LANGUAGE_STORAGE_KEY = "userLanguage";
|
||||
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Sbalit postranní panel",
|
||||
"expand_sidebar": "Rozbalit postranní panel",
|
||||
"edition_badge": "Otevřít modal placených plánů"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Vymazat e-mail",
|
||||
"show_password": "Zobrazit heslo",
|
||||
"hide_password": "Skrýt heslo",
|
||||
"close_alert": "Zavřít upozornění",
|
||||
"close_popover": "Zavřít vyskakovací okno"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -848,7 +848,6 @@
|
||||
"live": "Živě",
|
||||
"change_history": "Historie změn",
|
||||
"coming_soon": "Již brzy",
|
||||
"member": "Člen",
|
||||
"members": "Členové",
|
||||
"you": "Vy",
|
||||
"upgrade_cta": {
|
||||
@@ -1081,9 +1080,7 @@
|
||||
"select": {
|
||||
"error": "Vyberte alespoň jednu pracovní položku",
|
||||
"empty": "Nevybrány žádné pracovní položky",
|
||||
"add_selected": "Přidat vybrané pracovní položky",
|
||||
"select_all": "Vybrat vše",
|
||||
"deselect_all": "Zrušit výběr všeho"
|
||||
"add_selected": "Přidat vybrané pracovní položky"
|
||||
},
|
||||
"open_in_full_screen": "Otevřít pracovní položku na celou obrazovku"
|
||||
},
|
||||
@@ -2462,9 +2459,5 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Seitenleiste einklappen",
|
||||
"expand_sidebar": "Seitenleiste ausklappen",
|
||||
"edition_badge": "Modal für kostenpflichtige Pläne öffnen"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "E-Mail löschen",
|
||||
"show_password": "Passwort anzeigen",
|
||||
"hide_password": "Passwort verbergen",
|
||||
"close_alert": "Warnung schließen",
|
||||
"close_popover": "Popover schließen"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -848,7 +848,6 @@
|
||||
"live": "Live",
|
||||
"change_history": "Änderungsverlauf",
|
||||
"coming_soon": "Demnächst verfügbar",
|
||||
"member": "Mitglied",
|
||||
"members": "Mitglieder",
|
||||
"you": "Sie",
|
||||
"upgrade_cta": {
|
||||
@@ -1081,9 +1080,7 @@
|
||||
"select": {
|
||||
"error": "Wählen Sie mindestens ein Arbeitselement aus",
|
||||
"empty": "Keine Arbeitselemente ausgewählt",
|
||||
"add_selected": "Ausgewählte Arbeitselemente hinzufügen",
|
||||
"select_all": "Alle auswählen",
|
||||
"deselect_all": "Alle abwählen"
|
||||
"add_selected": "Ausgewählte Arbeitselemente hinzufügen"
|
||||
},
|
||||
"open_in_full_screen": "Arbeitselement im Vollbild öffnen"
|
||||
},
|
||||
@@ -2461,9 +2458,5 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Collapse sidebar",
|
||||
"expand_sidebar": "Expand sidebar",
|
||||
"edition_badge": "Open paid plans' modal"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Clear email",
|
||||
"show_password": "Show password",
|
||||
"hide_password": "Hide password",
|
||||
"close_alert": "Close alert",
|
||||
"close_popover": "Close popover"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -43,8 +43,7 @@
|
||||
"your_account": "Your account",
|
||||
"security": "Security",
|
||||
"activity": "Activity",
|
||||
"preferences": "Preferences",
|
||||
"language_and_time": "Language & Time",
|
||||
"appearance": "Appearance",
|
||||
"notifications": "Notifications",
|
||||
"workspaces": "Workspaces",
|
||||
"create_workspace": "Create workspace",
|
||||
@@ -57,10 +56,6 @@
|
||||
"something_went_wrong_please_try_again": "Something went wrong. Please try again.",
|
||||
"load_more": "Load more",
|
||||
"select_or_customize_your_interface_color_scheme": "Select or customize your interface color scheme.",
|
||||
"timezone_setting": "Current timezone setting.",
|
||||
"language_setting": "Choose the language used in the user interface.",
|
||||
"settings_moved_to_preferences": "Timezone & Language settings have been moved to preferences.",
|
||||
"go_to_preferences": "Go to preferences",
|
||||
"theme": "Theme",
|
||||
"system_preference": "System preference",
|
||||
"light": "Light",
|
||||
@@ -339,8 +334,6 @@
|
||||
"new_password_must_be_different_from_old_password": "New password must be different from old password",
|
||||
"edited": "edited",
|
||||
"bot": "Bot",
|
||||
"settings_description": "Manage your account, workspace, and project preferences all in one place. Switch between tabs to easily configure.",
|
||||
"back_to_workspace": "Back to workspace",
|
||||
"project_view": {
|
||||
"sort_by": {
|
||||
"created_at": "Created at",
|
||||
@@ -690,7 +683,6 @@
|
||||
"live": "Live",
|
||||
"change_history": "Change History",
|
||||
"coming_soon": "Coming soon",
|
||||
"member": "Member",
|
||||
"members": "Members",
|
||||
"you": "You",
|
||||
"upgrade_cta": {
|
||||
@@ -924,9 +916,7 @@
|
||||
"select": {
|
||||
"error": "Please select at least one work item",
|
||||
"empty": "No work items selected",
|
||||
"add_selected": "Add selected work items",
|
||||
"select_all": "Select all",
|
||||
"deselect_all": "Deselect all"
|
||||
"add_selected": "Add selected work items"
|
||||
},
|
||||
"open_in_full_screen": "Open work item in full screen"
|
||||
},
|
||||
@@ -1311,28 +1301,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"account_settings": {
|
||||
"profile":{},
|
||||
"preferences":{
|
||||
"heading": "Preferences",
|
||||
"description": "Customize your app experience the way you work"
|
||||
},
|
||||
"notifications":{
|
||||
"heading": "Email notifications",
|
||||
"description": "Stay in the loop on Work items you are subscribed to. Enable this to get notified."
|
||||
},
|
||||
"security":{
|
||||
"heading": "Security"
|
||||
},
|
||||
"api_tokens":{
|
||||
"heading": "Personal Access Tokens",
|
||||
"description": "Generate secure API tokens to integrate your data with external systems and applications."
|
||||
},
|
||||
"activity":{
|
||||
"heading": "Activity",
|
||||
"description": "Track your recent actions and changes across all projects and work items."
|
||||
}
|
||||
},
|
||||
"workspace_settings": {
|
||||
"label": "Workspace settings",
|
||||
"page_label": "{workspace} - General settings",
|
||||
@@ -1399,22 +1367,16 @@
|
||||
}
|
||||
},
|
||||
"billing_and_plans": {
|
||||
"heading": "Billing & Plans",
|
||||
"description":"Choose your plan, manage subscriptions, and easily upgrade as your needs grow.",
|
||||
"title": "Billing & Plans",
|
||||
"current_plan": "Current plan",
|
||||
"free_plan": "You are currently using the free plan",
|
||||
"view_plans": "View plans"
|
||||
},
|
||||
"exports": {
|
||||
"heading": "Exports",
|
||||
"description": "Export your project data in various formats and access your export history with download links.",
|
||||
"title": "Exports",
|
||||
"exporting": "Exporting",
|
||||
"previous_exports": "Previous exports",
|
||||
"export_separate_files": "Export the data into separate files",
|
||||
"exporting_projects": "Exporting project",
|
||||
"format": "Format",
|
||||
"modal": {
|
||||
"title": "Export to",
|
||||
"toasts": {
|
||||
@@ -1430,8 +1392,6 @@
|
||||
}
|
||||
},
|
||||
"webhooks": {
|
||||
"heading": "Webhooks",
|
||||
"description": "Automate notifications to external services when project events occur.",
|
||||
"title": "Webhooks",
|
||||
"add_webhook": "Add webhook",
|
||||
"modal": {
|
||||
@@ -1483,29 +1443,29 @@
|
||||
}
|
||||
},
|
||||
"api_tokens": {
|
||||
"title": "Personal Access Tokens",
|
||||
"add_token": "Add personal access token",
|
||||
"title": "API Tokens",
|
||||
"add_token": "Add API token",
|
||||
"create_token": "Create token",
|
||||
"never_expires": "Never expires",
|
||||
"generate_token": "Generate token",
|
||||
"generating": "Generating",
|
||||
"delete": {
|
||||
"title": "Delete personal access token",
|
||||
"title": "Delete API token",
|
||||
"description": "Any application using this token will no longer have the access to Plane data. This action cannot be undone.",
|
||||
"success": {
|
||||
"title": "Success!",
|
||||
"message": "The token has been successfully deleted"
|
||||
"message": "The API token has been successfully deleted"
|
||||
},
|
||||
"error": {
|
||||
"title": "Error!",
|
||||
"message": "The token could not be deleted"
|
||||
"message": "The API token could not be deleted"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty_state": {
|
||||
"api_tokens": {
|
||||
"title": "No personal access tokens created",
|
||||
"title": "No API tokens created",
|
||||
"description": "Plane APIs can be used to integrate your data in Plane with any external system. Create a token to get started."
|
||||
},
|
||||
"webhooks": {
|
||||
@@ -1555,9 +1515,8 @@
|
||||
"profile": "Profile",
|
||||
"security": "Security",
|
||||
"activity": "Activity",
|
||||
"preferences": "Preferences",
|
||||
"notifications": "Notifications",
|
||||
"api-tokens": "Personal Access Tokens"
|
||||
"appearance": "Appearance",
|
||||
"notifications": "Notifications"
|
||||
},
|
||||
"tabs": {
|
||||
"summary": "Summary",
|
||||
@@ -1619,8 +1578,6 @@
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"heading": "States",
|
||||
"description": "Define and customize workflow states to track the progress of your work items.",
|
||||
"describe_this_state_for_your_members": "Describe this state for your members.",
|
||||
"empty_state": {
|
||||
"title": "No states available for the {groupKey} group",
|
||||
@@ -1628,8 +1585,6 @@
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"heading": "Labels",
|
||||
"description": "Create custom labels to categorize and organize your work items",
|
||||
"label_title": "Label title",
|
||||
"label_title_is_required": "Label title is required",
|
||||
"label_max_char": "Label name should not exceed 255 characters",
|
||||
@@ -1638,11 +1593,9 @@
|
||||
}
|
||||
},
|
||||
"estimates": {
|
||||
"heading": "Estimates",
|
||||
"description": "Set up estimation systems to track and communicate the effort required for each work item.",
|
||||
"label": "Estimates",
|
||||
"title": "Enable estimates for my project",
|
||||
"enable_description": "They help you in communicating complexity and workload of the team.",
|
||||
"description": "They help you in communicating complexity and workload of the team.",
|
||||
"no_estimate": "No estimate",
|
||||
"new": "New estimate system",
|
||||
"create": {
|
||||
@@ -1724,8 +1677,6 @@
|
||||
},
|
||||
"automations": {
|
||||
"label": "Automations",
|
||||
"heading": "Automations",
|
||||
"description": "Configure automated actions to streamline your project management workflow and reduce manual tasks.",
|
||||
"auto-archive": {
|
||||
"title": "Auto-archive closed work items",
|
||||
"description": "Plane will auto archive work items that have been completed or canceled.",
|
||||
@@ -2344,9 +2295,5 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Colapsar barra lateral",
|
||||
"expand_sidebar": "Expandir barra lateral",
|
||||
"edition_badge": "Abrir modal de planes de pago"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Limpiar correo electrónico",
|
||||
"show_password": "Mostrar contraseña",
|
||||
"hide_password": "Ocultar contraseña",
|
||||
"close_alert": "Cerrar alerta",
|
||||
"close_popover": "Cerrar ventana emergente"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -851,7 +851,6 @@
|
||||
"live": "En vivo",
|
||||
"change_history": "Historial de cambios",
|
||||
"coming_soon": "Próximamente",
|
||||
"member": "Miembro",
|
||||
"members": "Miembros",
|
||||
"you": "Tú",
|
||||
"upgrade_cta": {
|
||||
@@ -1084,9 +1083,7 @@
|
||||
"select": {
|
||||
"error": "Por favor selecciona al menos un elemento de trabajo",
|
||||
"empty": "No hay elementos de trabajo seleccionados",
|
||||
"add_selected": "Agregar elementos seleccionados",
|
||||
"select_all": "Seleccionar todo",
|
||||
"deselect_all": "Deseleccionar todo"
|
||||
"add_selected": "Agregar elementos seleccionados"
|
||||
},
|
||||
"open_in_full_screen": "Abrir elemento de trabajo en pantalla completa"
|
||||
},
|
||||
@@ -2464,9 +2461,5 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Réduire la barre latérale",
|
||||
"expand_sidebar": "Étendre la barre latérale",
|
||||
"edition_badge": "Ouvrir le modal des plans payants"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Effacer l'e-mail",
|
||||
"show_password": "Afficher le mot de passe",
|
||||
"hide_password": "Masquer le mot de passe",
|
||||
"close_alert": "Fermer l'alerte",
|
||||
"close_popover": "Fermer la fenêtre contextuelle"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -849,7 +849,6 @@
|
||||
"live": "En direct",
|
||||
"change_history": "Historique des modifications",
|
||||
"coming_soon": "À venir",
|
||||
"member": "Membre",
|
||||
"members": "Membres",
|
||||
"you": "Vous",
|
||||
"upgrade_cta": {
|
||||
@@ -1082,9 +1081,7 @@
|
||||
"select": {
|
||||
"error": "Veuillez sélectionner au moins un élément de travail",
|
||||
"empty": "Aucun élément de travail sélectionné",
|
||||
"add_selected": "Ajouter les éléments de travail sélectionnés",
|
||||
"select_all": "Sélectionner tout",
|
||||
"deselect_all": "Tout désélectionner"
|
||||
"add_selected": "Ajouter les éléments de travail sélectionnés"
|
||||
},
|
||||
"open_in_full_screen": "Ouvrir l'élément de travail en plein écran"
|
||||
},
|
||||
@@ -2462,9 +2459,5 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Tutup sidebar",
|
||||
"expand_sidebar": "Perluas sidebar",
|
||||
"edition_badge": "Buka modal paket berbayar"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Hapus email",
|
||||
"show_password": "Tampilkan kata sandi",
|
||||
"hide_password": "Sembunyikan kata sandi",
|
||||
"close_alert": "Tutup peringatan",
|
||||
"close_popover": "Tutup popover"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -848,7 +848,6 @@
|
||||
"live": "Langsung",
|
||||
"change_history": "Riwayat Perubahan",
|
||||
"coming_soon": "Segera hadir",
|
||||
"member": "Anggota",
|
||||
"members": "Anggota",
|
||||
"you": "Anda",
|
||||
"upgrade_cta": {
|
||||
@@ -1081,9 +1080,7 @@
|
||||
"select": {
|
||||
"error": "Silakan pilih setidaknya satu item kerja",
|
||||
"empty": "Tidak ada item kerja yang dipilih",
|
||||
"add_selected": "Tambah item kerja yang dipilih",
|
||||
"select_all": "Pilih semua item kerja",
|
||||
"deselect_all": "Batalkan pilihan semua item kerja"
|
||||
"add_selected": "Tambah item kerja yang dipilih"
|
||||
},
|
||||
"open_in_full_screen": "Buka item kerja dalam layar penuh"
|
||||
},
|
||||
@@ -2456,9 +2453,5 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Comprimi barra laterale",
|
||||
"expand_sidebar": "Espandi barra laterale",
|
||||
"edition_badge": "Apri modal piani a pagamento"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Cancella email",
|
||||
"show_password": "Mostra password",
|
||||
"hide_password": "Nascondi password",
|
||||
"close_alert": "Chiudi avviso",
|
||||
"close_popover": "Chiudi popover"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -847,7 +847,6 @@
|
||||
"live": "Live",
|
||||
"change_history": "Cronologia modifiche",
|
||||
"coming_soon": "Prossimamente",
|
||||
"member": "Membro",
|
||||
"members": "Membri",
|
||||
"you": "Tu",
|
||||
"upgrade_cta": {
|
||||
@@ -1080,9 +1079,7 @@
|
||||
"select": {
|
||||
"error": "Seleziona almeno un elemento di lavoro",
|
||||
"empty": "Nessun elemento di lavoro selezionato",
|
||||
"add_selected": "Aggiungi gli elementi di lavoro selezionati",
|
||||
"select_all": "Seleziona tutto",
|
||||
"deselect_all": "Deseleziona tutto"
|
||||
"add_selected": "Aggiungi gli elementi di lavoro selezionati"
|
||||
},
|
||||
"open_in_full_screen": "Apri l'elemento di lavoro a schermo intero"
|
||||
},
|
||||
@@ -2461,9 +2458,5 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "サイドバーを折りたたむ",
|
||||
"expand_sidebar": "サイドバーを展開",
|
||||
"edition_badge": "有料プランのモーダルを開く"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "メールをクリア",
|
||||
"show_password": "パスワードを表示",
|
||||
"hide_password": "パスワードを非表示",
|
||||
"close_alert": "アラートを閉じる",
|
||||
"close_popover": "ポップオーバーを閉じる"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -849,7 +849,6 @@
|
||||
"live": "ライブ",
|
||||
"change_history": "変更履歴",
|
||||
"coming_soon": "近日公開",
|
||||
"member": "メンバー",
|
||||
"members": "メンバー",
|
||||
"you": "あなた",
|
||||
"upgrade_cta": {
|
||||
@@ -1082,9 +1081,7 @@
|
||||
"select": {
|
||||
"error": "少なくとも1つの作業項目を選択してください",
|
||||
"empty": "作業項目が選択されていません",
|
||||
"add_selected": "選択した作業項目を追加",
|
||||
"select_all": "すべて選択",
|
||||
"deselect_all": "すべての選択を解除"
|
||||
"add_selected": "選択した作業項目を追加"
|
||||
},
|
||||
"open_in_full_screen": "作業項目をフルスクリーンで開く"
|
||||
},
|
||||
@@ -2462,9 +2459,5 @@
|
||||
"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を選択して確認してください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "사이드바 축소",
|
||||
"expand_sidebar": "사이드바 확장",
|
||||
"edition_badge": "유료 플랜 모달 열기"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "이메일 지우기",
|
||||
"show_password": "비밀번호 표시",
|
||||
"hide_password": "비밀번호 숨기기",
|
||||
"close_alert": "알림 닫기",
|
||||
"close_popover": "팝오버 닫기"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -850,7 +850,6 @@
|
||||
"live": "라이브",
|
||||
"change_history": "변경 기록",
|
||||
"coming_soon": "곧 출시",
|
||||
"member": "멤버",
|
||||
"members": "멤버",
|
||||
"you": "나",
|
||||
"upgrade_cta": {
|
||||
@@ -1083,9 +1082,7 @@
|
||||
"select": {
|
||||
"error": "최소 하나의 작업 항목을 선택하세요",
|
||||
"empty": "선택된 작업 항목 없음",
|
||||
"add_selected": "선택된 작업 항목 추가",
|
||||
"select_all": "모두 선택",
|
||||
"deselect_all": "모두 선택 해제"
|
||||
"add_selected": "선택된 작업 항목 추가"
|
||||
},
|
||||
"open_in_full_screen": "작업 항목을 전체 화면으로 열기"
|
||||
},
|
||||
@@ -2464,9 +2461,5 @@
|
||||
"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를 선택하세요."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Zwiń pasek boczny",
|
||||
"expand_sidebar": "Rozwiń pasek boczny",
|
||||
"edition_badge": "Otwórz modal płatnych planów"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Wyczyść e-mail",
|
||||
"show_password": "Pokaż hasło",
|
||||
"hide_password": "Ukryj hasło",
|
||||
"close_alert": "Zamknij alert",
|
||||
"close_popover": "Zamknij popover"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -850,7 +850,6 @@
|
||||
"live": "Na żywo",
|
||||
"change_history": "Historia zmian",
|
||||
"coming_soon": "Wkrótce",
|
||||
"member": "Członek",
|
||||
"members": "Członkowie",
|
||||
"you": "Ty",
|
||||
"upgrade_cta": {
|
||||
@@ -1083,9 +1082,7 @@
|
||||
"select": {
|
||||
"error": "Wybierz co najmniej jeden element pracy",
|
||||
"empty": "Nie wybrano żadnych elementów pracy",
|
||||
"add_selected": "Dodaj wybrane elementy pracy",
|
||||
"select_all": "Wybierz wszystko",
|
||||
"deselect_all": "Odznacz wszystko"
|
||||
"add_selected": "Dodaj wybrane elementy pracy"
|
||||
},
|
||||
"open_in_full_screen": "Otwórz element pracy na pełnym ekranie"
|
||||
},
|
||||
@@ -2463,9 +2460,5 @@
|
||||
"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ść."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Recolher barra lateral",
|
||||
"expand_sidebar": "Expandir barra lateral",
|
||||
"edition_badge": "Abrir modal de planos pagos"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Limpar e-mail",
|
||||
"show_password": "Mostrar senha",
|
||||
"hide_password": "Ocultar senha",
|
||||
"close_alert": "Fechar alerta",
|
||||
"close_popover": "Fechar popover"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -850,7 +850,6 @@
|
||||
"live": "Ao vivo",
|
||||
"change_history": "Histórico de alterações",
|
||||
"coming_soon": "Em breve",
|
||||
"member": "Membro",
|
||||
"members": "Membros",
|
||||
"you": "Você",
|
||||
"upgrade_cta": {
|
||||
@@ -1083,9 +1082,7 @@
|
||||
"select": {
|
||||
"error": "Selecione pelo menos um item de trabalho",
|
||||
"empty": "Nenhum item de trabalho selecionado",
|
||||
"add_selected": "Adicionar itens de trabalho selecionados",
|
||||
"select_all": "Selecionar tudo",
|
||||
"deselect_all": "Desmarcar tudo"
|
||||
"add_selected": "Adicionar itens de trabalho selecionados"
|
||||
},
|
||||
"open_in_full_screen": "Abrir item de trabalho em tela cheia"
|
||||
},
|
||||
@@ -2458,9 +2455,5 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Restrânge bara laterală",
|
||||
"expand_sidebar": "Extinde bara laterală",
|
||||
"edition_badge": "Deschide modalul planurilor plătite"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Șterge e-mailul",
|
||||
"show_password": "Afișează parola",
|
||||
"hide_password": "Ascunde parola",
|
||||
"close_alert": "Închide alerta",
|
||||
"close_popover": "Închide popover-ul"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -848,7 +848,6 @@
|
||||
"live": "În direct",
|
||||
"change_history": "Istoric modificări",
|
||||
"coming_soon": "În curând",
|
||||
"member": "Membru",
|
||||
"members": "Membri",
|
||||
"you": "Tu",
|
||||
"upgrade_cta": {
|
||||
@@ -1081,9 +1080,7 @@
|
||||
"select": {
|
||||
"error": "Selectează cel puțin o activitate",
|
||||
"empty": "Nicio activitate selectată",
|
||||
"add_selected": "Adaugă activitățile selectate",
|
||||
"select_all": "Selectează tot",
|
||||
"deselect_all": "Deselează tot"
|
||||
"add_selected": "Adaugă activitățile selectate"
|
||||
},
|
||||
"open_in_full_screen": "Deschide activitatea pe tot ecranul"
|
||||
},
|
||||
@@ -2456,9 +2453,5 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Свернуть боковую панель",
|
||||
"expand_sidebar": "Развернуть боковую панель",
|
||||
"edition_badge": "Открыть модал платных планов"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Очистить email",
|
||||
"show_password": "Показать пароль",
|
||||
"hide_password": "Скрыть пароль",
|
||||
"close_alert": "Закрыть уведомление",
|
||||
"close_popover": "Закрыть всплывающее окно"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -850,7 +850,6 @@
|
||||
"live": "В прямом эфире",
|
||||
"change_history": "История изменений",
|
||||
"coming_soon": "Скоро",
|
||||
"member": "Участник",
|
||||
"members": "Участники",
|
||||
"you": "Вы",
|
||||
"upgrade_cta": {
|
||||
@@ -1083,9 +1082,7 @@
|
||||
"select": {
|
||||
"error": "Выберите хотя бы один рабочий элемент",
|
||||
"empty": "Рабочие элементы не выбраны",
|
||||
"add_selected": "Добавить выбранные рабочие элементы",
|
||||
"select_all": "Выбрать все",
|
||||
"deselect_all": "Снять выделение со всех"
|
||||
"add_selected": "Добавить выбранные рабочие элементы"
|
||||
},
|
||||
"open_in_full_screen": "Открыть рабочий элемент в полном экране"
|
||||
},
|
||||
@@ -2464,9 +2461,5 @@
|
||||
"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, чтобы убедиться."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Zbaliť bočný panel",
|
||||
"expand_sidebar": "Rozbaliť bočný panel",
|
||||
"edition_badge": "Otvoriť modal platených plánov"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Vymazať e-mail",
|
||||
"show_password": "Zobraziť heslo",
|
||||
"hide_password": "Skryť heslo",
|
||||
"close_alert": "Zavrieť upozornenie",
|
||||
"close_popover": "Zavrieť vyskakovacie okno"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -850,7 +850,6 @@
|
||||
"live": "Živé",
|
||||
"change_history": "História zmien",
|
||||
"coming_soon": "Už čoskoro",
|
||||
"member": "Člen",
|
||||
"members": "Členovia",
|
||||
"you": "Vy",
|
||||
"upgrade_cta": {
|
||||
@@ -1083,9 +1082,7 @@
|
||||
"select": {
|
||||
"error": "Vyberte aspoň jednu pracovnú položku",
|
||||
"empty": "Nie sú vybrané žiadne pracovné položky",
|
||||
"add_selected": "Pridať vybrané pracovné položky",
|
||||
"select_all": "Vybrať všetko",
|
||||
"deselect_all": "Zrušiť výber všetkého"
|
||||
"add_selected": "Pridať vybrané pracovné položky"
|
||||
},
|
||||
"open_in_full_screen": "Otvoriť pracovnú položku na celú obrazovku"
|
||||
},
|
||||
@@ -2463,9 +2460,5 @@
|
||||
"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í."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Kenar çubuğunu daralt",
|
||||
"expand_sidebar": "Kenar çubuğunu genişlet",
|
||||
"edition_badge": "Ücretli planlar modalını aç"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "E-postayı temizle",
|
||||
"show_password": "Şifreyi göster",
|
||||
"hide_password": "Şifreyi gizle",
|
||||
"close_alert": "Uyarıyı kapat",
|
||||
"close_popover": "Açılır pencereyi kapat"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -851,7 +851,6 @@
|
||||
"live": "Canlı",
|
||||
"change_history": "Değişiklik Geçmişi",
|
||||
"coming_soon": "Çok Yakında",
|
||||
"member": "Üye",
|
||||
"members": "Üyeler",
|
||||
"you": "Siz",
|
||||
"upgrade_cta": {
|
||||
@@ -1084,9 +1083,7 @@
|
||||
"select": {
|
||||
"error": "Lütfen en az bir iş öğesi seçin",
|
||||
"empty": "Hiç iş öğesi seçilmedi",
|
||||
"add_selected": "Seçilen iş öğelerini ekle",
|
||||
"select_all": "Tümünü seç",
|
||||
"deselect_all": "Tümünü seçme"
|
||||
"add_selected": "Seçilen iş öğelerini ekle"
|
||||
},
|
||||
"open_in_full_screen": "İş öğesini tam ekranda aç"
|
||||
},
|
||||
@@ -2442,9 +2439,5 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Згорнути бічну панель",
|
||||
"expand_sidebar": "Розгорнути бічну панель",
|
||||
"edition_badge": "Відкрити модал платних планів"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Очистити email",
|
||||
"show_password": "Показати пароль",
|
||||
"hide_password": "Приховати пароль",
|
||||
"close_alert": "Закрити сповіщення",
|
||||
"close_popover": "Закрити спливаюче вікно"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -850,7 +850,6 @@
|
||||
"live": "Наживо",
|
||||
"change_history": "Історія змін",
|
||||
"coming_soon": "Незабаром",
|
||||
"member": "Учасник",
|
||||
"members": "Учасники",
|
||||
"you": "Ви",
|
||||
"upgrade_cta": {
|
||||
@@ -1083,9 +1082,7 @@
|
||||
"select": {
|
||||
"error": "Виберіть принаймні одну робочу одиницю",
|
||||
"empty": "Не вибрано жодної робочої одиниці",
|
||||
"add_selected": "Додати вибрані робочі одиниці",
|
||||
"select_all": "Вибрати всі",
|
||||
"deselect_all": "Скасувати вибір усіх"
|
||||
"add_selected": "Додати вибрані робочі одиниці"
|
||||
},
|
||||
"open_in_full_screen": "Відкрити робочу одиницю на повний екран"
|
||||
},
|
||||
@@ -2463,9 +2460,5 @@
|
||||
"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, щоб переконатися."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "Thu gọn thanh bên",
|
||||
"expand_sidebar": "Mở rộng thanh bên",
|
||||
"edition_badge": "Mở modal gói trả phí"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "Xóa email",
|
||||
"show_password": "Hiển thị mật khẩu",
|
||||
"hide_password": "Ẩn mật khẩu",
|
||||
"close_alert": "Đóng cảnh báo",
|
||||
"close_popover": "Đóng popover"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -849,7 +849,6 @@
|
||||
"live": "Trực tiếp",
|
||||
"change_history": "Lịch sử thay đổi",
|
||||
"coming_soon": "Sắp ra mắt",
|
||||
"member": "Thành viên",
|
||||
"members": "Thành viên",
|
||||
"you": "Bạn",
|
||||
"upgrade_cta": {
|
||||
@@ -1082,9 +1081,7 @@
|
||||
"select": {
|
||||
"error": "Vui lòng chọn ít nhất một mục công việc",
|
||||
"empty": "Chưa chọn mục công việc",
|
||||
"add_selected": "Thêm mục công việc đã chọn",
|
||||
"select_all": "Chọn tất cả",
|
||||
"deselect_all": "Bỏ chọn tất cả"
|
||||
"add_selected": "Thêm mục công việc đã chọn"
|
||||
},
|
||||
"open_in_full_screen": "Mở mục công việc trong chế độ toàn màn hình"
|
||||
},
|
||||
@@ -2461,9 +2458,5 @@
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "折叠侧边栏",
|
||||
"expand_sidebar": "展开侧边栏",
|
||||
"edition_badge": "打开付费计划模态框"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "清除邮箱",
|
||||
"show_password": "显示密码",
|
||||
"hide_password": "隐藏密码",
|
||||
"close_alert": "关闭警告",
|
||||
"close_popover": "关闭弹出框"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -849,7 +849,6 @@
|
||||
"live": "实时",
|
||||
"change_history": "变更历史",
|
||||
"coming_soon": "即将推出",
|
||||
"member": "成员",
|
||||
"members": "成员",
|
||||
"you": "你",
|
||||
"upgrade_cta": {
|
||||
@@ -1082,9 +1081,7 @@
|
||||
"select": {
|
||||
"error": "请至少选择一个工作项",
|
||||
"empty": "未选择工作项",
|
||||
"add_selected": "添加所选工作项",
|
||||
"select_all": "全选",
|
||||
"deselect_all": "取消全选"
|
||||
"add_selected": "添加所选工作项"
|
||||
},
|
||||
"open_in_full_screen": "在全屏中打开工作项"
|
||||
},
|
||||
@@ -2443,9 +2440,5 @@
|
||||
"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 日志,以确认问题。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,6 @@
|
||||
"collapse_sidebar": "摺疊側邊欄",
|
||||
"expand_sidebar": "展開側邊欄",
|
||||
"edition_badge": "打開付費計劃模態框"
|
||||
},
|
||||
"auth_forms": {
|
||||
"clear_email": "清除電子郵件",
|
||||
"show_password": "顯示密碼",
|
||||
"hide_password": "隱藏密碼",
|
||||
"close_alert": "關閉警告",
|
||||
"close_popover": "關閉彈出框"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -850,7 +850,6 @@
|
||||
"live": "即時",
|
||||
"change_history": "變更歷史記錄",
|
||||
"coming_soon": "即將推出",
|
||||
"member": "成員",
|
||||
"members": "成員",
|
||||
"you": "您",
|
||||
"upgrade_cta": {
|
||||
@@ -1083,9 +1082,7 @@
|
||||
"select": {
|
||||
"error": "請至少選擇一個工作事項",
|
||||
"empty": "未選擇工作事項",
|
||||
"add_selected": "新增已選取的工作事項",
|
||||
"select_all": "全選",
|
||||
"deselect_all": "取消全選"
|
||||
"add_selected": "新增已選取的工作事項"
|
||||
},
|
||||
"open_in_full_screen": "以全螢幕開啟工作事項"
|
||||
},
|
||||
@@ -2464,10 +2461,5 @@
|
||||
"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 來確認。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
"main": "tailwind.config.js",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.38",
|
||||
|
||||
@@ -469,7 +469,6 @@ module.exports = {
|
||||
plugins: [
|
||||
require("tailwindcss-animate"),
|
||||
require("@tailwindcss/typography"),
|
||||
require("@tailwindcss/container-queries"),
|
||||
function ({ addUtilities }) {
|
||||
const newUtilities = {
|
||||
// Mobile screens
|
||||
|
||||
+33
-14
@@ -1,5 +1,14 @@
|
||||
import { EUserProjectRoles } from "@plane/constants";
|
||||
import type { IUser, IUserLite, IWorkspace, TLogoProps, TStateGroups } from "..";
|
||||
import type {
|
||||
IProjectViewProps,
|
||||
IUser,
|
||||
IUserLite,
|
||||
IUserMemberLite,
|
||||
IWorkspace,
|
||||
IWorkspaceLite,
|
||||
TLogoProps,
|
||||
TStateGroups,
|
||||
} from "..";
|
||||
import { TUserPermissions } from "../enums";
|
||||
|
||||
export interface IPartialProject {
|
||||
@@ -82,20 +91,30 @@ export interface IProjectMemberLite {
|
||||
member_id: string;
|
||||
}
|
||||
|
||||
export type TProjectMembership = {
|
||||
export interface IProjectMember {
|
||||
id: string;
|
||||
member: IUserMemberLite;
|
||||
project: IProjectLite;
|
||||
workspace: IWorkspaceLite;
|
||||
comment: string;
|
||||
role: TUserPermissions;
|
||||
|
||||
preferences: ProjectPreferences;
|
||||
|
||||
view_props: IProjectViewProps;
|
||||
default_props: IProjectViewProps;
|
||||
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
created_by: string;
|
||||
updated_by: string;
|
||||
}
|
||||
|
||||
export interface IProjectMembership {
|
||||
id: string;
|
||||
member: string;
|
||||
role: TUserPermissions | EUserProjectRoles;
|
||||
created_at: string;
|
||||
} & (
|
||||
| {
|
||||
id: string;
|
||||
original_role: EUserProjectRoles;
|
||||
}
|
||||
| {
|
||||
id: null;
|
||||
original_role: null;
|
||||
}
|
||||
);
|
||||
role: TUserPermissions;
|
||||
}
|
||||
|
||||
export interface IProjectBulkAddFormData {
|
||||
members: { role: TUserPermissions | EUserProjectRoles; member_id: string }[];
|
||||
|
||||
Vendored
-3
@@ -12,7 +12,6 @@ export interface IUserLite {
|
||||
id: string;
|
||||
is_bot: boolean;
|
||||
last_name: string;
|
||||
joining_date?: string;
|
||||
}
|
||||
export interface IUser extends IUserLite {
|
||||
// only for uploading the cover image
|
||||
@@ -79,8 +78,6 @@ export interface IUserSettings {
|
||||
workspace: {
|
||||
last_workspace_id: string | undefined;
|
||||
last_workspace_slug: string | undefined;
|
||||
last_workspace_name: string | undefined;
|
||||
last_workspace_logo: string | undefined;
|
||||
fallback_workspace_id: string | undefined;
|
||||
fallback_workspace_slug: string | undefined;
|
||||
invites: number | undefined;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user