Compare commits

...
Author SHA1 Message Date
Anmol Singh Bhatia 12f5be7c8c Merge branch 'preview' of github.com:makeplane/plane into feat-breadcrumbs-revamp 2025-06-18 15:34:43 +05:30
JayashTripathyandGitHub 9cdfb2224a [WEB-4160]: Context menu close after clicking on menu item of project #7231 2025-06-18 15:33:06 +05:30
Anmol Singh Bhatia ae0867e727 chore: code refactor 2025-06-18 15:29:12 +05:30
SangeethaandGitHub 8129f5f969 [WEB-4340] fix: duplicate assignees in user recents (#7216)
* fix: duplicate assignees in user recents

* chore: optimize filtering logic

* chore: filter with deleted_at field

* chore: tests for IssueRecentSerializer
2025-06-18 15:14:21 +05:30
Anmol Singh Bhatia 16652a6b4c chore: code refactor 2025-06-18 15:10:04 +05:30
Anmol Singh Bhatia dacc9178ae chore: code refactor 2025-06-18 15:04:49 +05:30
Anmol Singh Bhatia 08f47b55e1 chore: code refactor 2025-06-17 18:38:46 +05:30
Prateek ShouryaandGitHub 89b8cdbe6e [WEB-4335] improvement: optimize assignee grouping with improved member scope handling (#7227) 2025-06-17 17:17:04 +05:30
Prateek ShouryaandGitHub 53e6a62a12 fix: move lucide related constants to ui package (#7226)
* fix: move lucide related constants to ui package

* chore: update yarn.lock
2025-06-17 17:06:05 +05:30
Anmol Singh Bhatia b3a9670225 chore: workspace view breadcrumb improvements 2025-06-17 16:03:41 +05:30
Anmol Singh Bhatia 51d76f1b0b Merge branch 'preview' of github.com:makeplane/plane into feat-breadcrumbs-revamp 2025-06-17 15:58:37 +05:30
Prateek ShouryaandGitHub 75f89c4c12 fix: docker build (#7220)
* fix: docker build

* fix: build
2025-06-17 14:08:50 +05:30
Anmol Singh Bhatia ace3df4bc5 chore: code refactor 2025-06-16 20:30:12 +05:30
Anmol Singh Bhatia cb658c4b98 fix: nprogress and button tooltip 2025-06-16 19:55:06 +05:30
Anmol Singh Bhatia 825855153e fix: build error 2025-06-16 19:33:00 +05:30
Anmol Singh Bhatia 12c3e91c4b Merge branch 'preview' of github.com:makeplane/plane into feat-breadcrumbs-revamp 2025-06-16 19:13:37 +05:30
0983e5f44d [WEB-4281] chore: project error message updated (#7190)
* chore: project error message updated

* fix: error message for project creation

* fix: incorrect error code

* chore: code refactor

* chore: code refactor

---------

Co-authored-by: sangeethailango <sangeethailango21@gmail.com>
2025-06-16 17:19:44 +05:30
Prateek ShouryaandGitHub 2014400bed refactor: move web utils to packages (#7145)
* refactor: move web utils to packages

* fix: build and lint errors

* chore: update drag handle plugin

* chore: update table cell type to fix build errors

* fix: build errors

* chore: sync few changes

* fix: build errors

* chore: minor fixes related to duplicate assets imports

* fix: build errors

* chore: minor changes
2025-06-16 17:18:41 +05:30
sriram veeraghanta dffcc6dc10 chore(deps): brace-expansion upgraded to 2.0.2 2025-06-16 17:10:08 +05:30
sriram veeraghanta 640b23fb1b chore(deps): nextjs upgrade to 14.2.30 2025-06-16 17:02:04 +05:30
JayashTripathyandGitHub e13d8aa4b3 [WEB-4231] Pie chart tooltip #7192 2025-06-16 14:03:07 +05:30
Prateek ShouryaandGitHub cf595de7c7 [WEB-4311] fix: membership data handling and state reversal on error (#7205) 2025-06-16 14:02:47 +05:30
JayashTripathyandGitHub 0fa9c8b015 [WEB-4323] refactor: Analytics refactor (#7213)
* chore: updated label for epics

* chore: improved export logic

* refactor: move csvConfig to export.ts and clean up export logic

* refactor: remove unused CSV export logic from WorkItemsInsightTable component

* refactor: streamline data handling in InsightTable component for improved rendering

* feat: add translation for "No. of {entity}" and update priority chart y-axis label to use new translation

* refactor: cleaned up some component and added utilitites

* feat: add "at_risk" translation to multiple languages in translations.json files

* refactor: update TrendPiece component to use new status variants for analytics

* fix: adjust TrendPiece component logic for on-track and off-track status

* refactor: use nullish coalescing operator for yAxis.dx in line and scatter charts

* feat: add "at_risk" translation to various languages in translations.json files

* feat: add "no_of" translation to various languages in translations.json files

* feat: update "at_risk" translation in Ukrainian, Vietnamese, and Chinese locales in translations.json files

* refactor: rename insightsFields to ANALYTICS_INSIGHTS_FIELDS and update analytics tab import to use getAnalyticsTabs function

* feat: update AnalyticsWrapper to use i18n for titles and add new translation for "no_of" in Russian

* fix: update yAxis labels and offsets in various charts to use new translation key and improve layout

* feat: define AnalyticsTab interface and refactor getAnalyticsTabs function for improved type safety

* fix: update AnalyticsTab interface to use TAnalyticsTabsBase for improved type safety

* fix: add whitespace-nowrap class to TableHead for improved header layout in DataTable component
2025-06-16 14:01:49 +05:30
Aaryan KhandelwalandGitHub 6fe0415d66 [WEB-4316] chore: new endpoints to download an asset (#7207)
* chore: new endpoints to download an asset

* chore: add exception handling
2025-06-13 14:41:08 +05:30
sriram veeraghantaandGitHub ebc2bdcd3a feat: adding build process to logger package using tsup #7210 2025-06-13 01:50:44 +05:30
AaronandGitHub 11b222ece8 chore(deps): update TypeScript version across multiple packages to 5.8.3 (#7209) 2025-06-13 01:40:27 +05:30
JayashTripathyandGitHub c1a078ef3f [WEB-4246] Analytics minor improvements (#7194)
* chore: updated label for epics

* chore: improved export logic

* refactor: move csvConfig to export.ts and clean up export logic

* refactor: remove unused CSV export logic from WorkItemsInsightTable component

* refactor: streamline data handling in InsightTable component for improved rendering

* feat: add translation for "No. of {entity}" and update priority chart y-axis label to use new translation

* refactor: cleaned up some component and added utilitites

* feat: add "at_risk" translation to multiple languages in translations.json files

* refactor: update TrendPiece component to use new status variants for analytics

* fix: adjust TrendPiece component logic for on-track and off-track status

* refactor: use nullish coalescing operator for yAxis.dx in line and scatter charts

* feat: add "at_risk" translation to various languages in translations.json files

* feat: add "no_of" translation to various languages in translations.json files

* feat: update "at_risk" translation in Ukrainian, Vietnamese, and Chinese locales in translations.json files
2025-06-12 21:15:09 +05:30
Anmol Singh Bhatia dc6660db78 chore: code refactor 2025-06-11 17:22:17 +05:30
Akshita GoyalandGitHub ad11a34efc [WEB-4236] fix: divided settings scroll for sidebar and main content (#7201)
* fix: divided settings scroll for sidebar and main content

* fix: handled icons

* fix: mobile css
2025-06-11 16:11:40 +05:30
Prateek ShouryaandGitHub 9c28db8b7b [WEB-4300] improvement: add allowedProjectIds to create work item modal (#7195) 2025-06-10 20:32:39 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
32d5fea3d3 chore(deps): bump requests (#7193)
Bumps the pip group with 1 update in the /apiserver/requirements directory: [requests](https://github.com/psf/requests).


Updates `requests` from 2.32.2 to 2.32.4
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.32.4
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-10 17:39:48 +05:30
Anmol Singh Bhatia f188542383 chore: code refactor 2025-06-10 16:42:37 +05:30
Anmol Singh Bhatia 0a73fa64ee Merge branch 'preview' of github.com:makeplane/plane into feat-breadcrumbs-revamp 2025-06-10 14:25:07 +05:30
Prateek ShouryaandGitHub 6adc721b34 [WEB-4283] fix: update group key handling in issue store utilities for state groups (#7191)
* fix: update group key handling in issue store utilities for state groups

- Introduced a new function to determine the default group key based on the provided groupByKey.
- Updated references to use the new function for improved clarity and maintainability.
- Adjusted the mapping for "state_detail.group" in the ISSUE_GROUP_BY_KEY to ensure consistency.
- Enhanced the getArrayStringArray method to handle group values more effectively.

* refactor: clean up filters constants
2025-06-10 13:56:42 +05:30
Anmol Singh BhatiaandGitHub 531748dcc3 [WEB-4288] fix: auth page tab index (#7189)
* fix: auth page tab index

* chore: code refactor
2025-06-10 01:47:59 +05:30
Anmol Singh Bhatia cb6d9397cf Merge branch 'preview' of github.com:makeplane/plane into feat-breadcrumbs-revamp 2025-06-09 18:04:59 +05:30
Anmol Singh Bhatia 92559cc37b chore: breadcrumb refactoring 2025-06-09 17:57:53 +05:30
Anmol Singh Bhatia 831c255184 chore: common breadcrumb component added 2025-06-09 17:56:51 +05:30
Anmol Singh Bhatia aef81e2681 chore: project navigation helper function added 2025-06-09 17:53:39 +05:30
Anmol Singh Bhatia 9cc484551e chore: switch label and breadcrumb link component refactor 2025-06-09 17:53:14 +05:30
Anmol Singh Bhatia cb21fe4ea2 chore: breadcrumb stories added 2025-06-09 17:38:49 +05:30
Anmol Singh Bhatia 5cdf1a6351 chore: custom search select component refactoring 2025-06-09 17:38:10 +05:30
Anmol Singh Bhatia c669643198 feat: revamp breadcrumb and add navigation dropdown component 2025-06-09 17:37:18 +05:30
Anmol Singh Bhatia 823885c872 chore: project feature enum added 2025-06-09 17:32:05 +05:30
Sangmin AhnandGitHub 9965f48ba7 fix: prevent prematurely triggered Japanese label creation (#7084) 2025-06-09 16:07:42 +05:30
Saurabh KumarandGitHub d15d7549f7 [SILO-303] Add external id and external source in project model #7182 2025-06-09 16:02:09 +05:30
Vamsi KrishnaandGitHub 8fcffd2338 [WEB-4196]fix: sub work item copy link message #7186 2025-06-09 15:46:57 +05:30
Vamsi KrishnaandGitHub 07e937cd8e [WEB-4094]chore: workspace notifications refactor (#7061)
* chore: workspace notifications refactor

* fix: url params

* fix: added null checks to avoid run time errors

* fix: notifications header color fix
2025-06-09 15:33:57 +05:30
Farahat AbdrabouhandGitHub 1f1b421735 Docs: Correct numeric values in contributing guide #7184 2025-06-09 13:22:07 +05:30
sriram veeraghanta 5a43ec8411 chore: turbo repo version upgrade 2025-06-09 13:20:07 +05:30
sriram veeraghanta c86e7e02bc chore: upgrade tar-fs package to fix vulnerabilities 2025-06-09 13:19:14 +05:30
sriram veeraghanta d91d7a2f60 chore: tar-fs patch upgrade 2025-06-09 12:58:18 +05:30
sriram veeraghanta b3b285b1e5 chore: upgrade django version to 4.2.22 2025-06-09 12:49:26 +05:30
Prateek ShouryaandGitHub 11debee402 fix: build errors related to project member list (#7185) 2025-06-09 00:31:27 +05:30
Vamsi KrishnaandGitHub 1608e4f122 [WEB-3374]feat: added merge date display (#7141)
* feat: added merge date display

* chore: moved formatter ti utils

* chore: removed unwanted props
2025-06-08 23:47:08 +05:30
edeeee1227 [WEB-4063]chore: updated work item email template (#7044)
* chore: updated work item email template

* chore: passed dynamic value for email template

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2025-06-08 23:46:12 +05:30
sriram veeraghanta 9ff238816b sync: canary changes to preview 2025-06-06 18:06:51 +05:30
sriram veeraghanta 6bd5caf008 chore: upgrade package version 2025-06-06 17:50:31 +05:30
sriram veeraghanta c021aff58f chore: django version upgrade 2025-06-06 16:04:34 +05:30
sriram veeraghanta 683be55883 chore: upgrade nextjs version 2025-06-06 16:02:56 +05:30
Manish Guptaandsriram veeraghanta 970ce8cf26 [INFRA-183] feat: add restore-airgapped script to build workflow (#7170)
* [WEB-4260] chore: add restore-airgapped script to build workflow

* docs: update restore instructions in README for self-hosted and commercial air-gapped versions

* fix: update restore script filename and improve error handling in restore-airgapped script
2025-06-06 15:24:43 +05:30
Manish Guptaandsriram veeraghanta cbbe1a4e4d refactor: Enhance backup and restore scripts for container data (#7055)
* refactor: enhance backup and restore scripts for container data management

* fix: ensure proper quoting in backup script to handle paths with spaces

* fix: ensure backup directory is only removed if tar command succeeds

* CodeRabbit fixes
2025-06-06 15:24:43 +05:30
Manish Guptaandsriram veeraghanta 6a74677cc9 fix: update API service startup check to use HTTP request instead of logs (#7054) 2025-06-06 15:24:43 +05:30
sriram veeraghanta f6ea4f931d Merge branch 'canary' of github.com:makeplane/plane into preview 2025-06-06 15:23:10 +05:30
Aaryan KhandelwalandGitHub 950fcfdb40 [WIKI-391] chore: handle deactivated user display name in version history #7171 2025-06-06 15:04:00 +05:30
Bavisetti NarayanandGitHub 053c895120 [WEB 4252] chore: updated the favicon request for work item link (#7173)
* chore: added the favicon to link

* chore: added none validation for soup
2025-06-06 15:02:00 +05:30
Aaryan KhandelwalandGitHub 245167e8aa refactor: unused components, hooks, constants (#7157)
* refactor: remove unused dashboard components and fetch keys

* refactor: remove unused hooks and wrappers

* chore: remove unused function
2025-06-06 14:09:56 +05:30
Vamsi KrishnaandGitHub 6be3f0ea73 [WEB-4208]chore: refactored work item quick actions (#7136)
* chore: refactored work item quick actions

* chore: update event handling for menu

* chore: reverted unwanted changes

* fix: update archive copy link

* chore: handled undefined function implementation
2025-06-06 13:21:00 +05:30
JayashTripathyandGitHub 14d2d69120 [WEB-4230] refactor: Analytics code refacor, Removal of nivo charts dependencies and translations (#7131)
* chore: added code split for the analytics store

* chore: done some refactor

* refactor: update entity keys in analytics and translations

* chore: updated the translations

* refactor: simplify AnalyticsStoreV2 class by removing unnecessary constructor

* feat: add AnalyticsStoreV2 class and interface for enhanced analytics functionality

* feat: enhance WorkItemsModal and analytics store with isEpic functionality

* feat: integrate isEpic state into TotalInsights and WorkItemsModal components

* refactor: remove isEpic state from WorkItemsModalMainContent component

* refactor: removed old  analytics components and related services

* refactor: new analytics

* refactor: removed all nivo chart dependencies

* chore: resolved coderabbit comments

* fix: update processUrl to handle custom-work-items in peek view

* feat: implement CSV export functionality in InsightTable component

* feat: enhance analytics service with filter parameters and improve data handling in InsightTable

* feat: add new translation keys for various statuses across multiple languages

* [WEB-4246] fix: enhance analytics components to include 'isEpic' parameter for improved data fetching

* chore: update yarn.lock to remove deprecated @nivo packages and clean up unused dependencies
2025-06-06 01:53:38 +05:30
Anmol Singh BhatiaandGitHub 570a9e319e [WEB-4257] chore: user profile setting options updated #7166 2025-06-06 01:47:31 +05:30
Anmol Singh BhatiaandGitHub 469a027bb6 [WEB-4274] fix: metadata base url warning #7175 2025-06-05 22:51:56 +05:30
Prateek ShouryaandGitHub 8c99a7df88 [WEB-4273] fix: plans comparison scroll issue (#7176) 2025-06-05 22:51:05 +05:30
Prateek ShouryaandGitHub f34f078bd2 [WEB-4272] fix: remove duplicate CommandPalette instances from settings layouts to prevent modal conflicts (#7174) 2025-06-05 20:48:36 +05:30
Anmol Singh BhatiaandGitHub 0fe2549bc6 [WEB-4256] chore: add og image and update meta tags for social media compatibility (#7165)
* chore: og image added

* chore: meta config for cross-platform support
2025-06-05 19:32:11 +05:30
Prateek ShouryaandGitHub 118964de01 [WEB-4254] fix: ensure user details are available in project member details computation (#7162) 2025-06-05 19:31:07 +05:30
Manish GuptaandGitHub 9f37f1ef0e [INFRA-183] feat: add restore-airgapped script to build workflow (#7170)
* [WEB-4260] chore: add restore-airgapped script to build workflow

* docs: update restore instructions in README for self-hosted and commercial air-gapped versions

* fix: update restore script filename and improve error handling in restore-airgapped script
2025-06-05 17:27:57 +05:30
Prateek ShouryaandGitHub 986f29d1f2 [WEB-4253] improvement: plan card enhancements (#7168)
* [WEB-4253] improvement: plan card enhancements

* improvement: pricing changes
2025-06-05 14:37:26 +05:30
Aaryan KhandelwalandGitHub 1113f9fc19 [WIKI-412] regression: drop plugin logic #7161 2025-06-04 19:07:49 +05:30
Prateek ShouryaandGitHub ef3ec7274c [WEB-4253] improvement: minor enhancements to billing page (#7160) 2025-06-04 17:29:45 +05:30
a0a45b7916 [WEB-4249] fix: settings header css + cta on error page + project member list (#7159)
* fix: settings header css + cta on error page

* [WEB-4249] fix: filter out inactive workspace members from project member list

---------

Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
2025-06-04 16:38:35 +05:30
Aaryan KhandelwalandGitHub 2792d48288 [WIKI-412] chore: improved rich text editor extensions handling (#7158)
* chore: code split for rich text editor extensions

* chore: update type

* chore: add missing prop
2025-06-04 15:32:54 +05:30
Anmol Singh BhatiaandGitHub b2ccca0567 [WEB-3931] chore: maintenance page ux copy (#7135)
* chore: maintenance ux copy translation added

* chore: maintenance ux copy updated

* chore: code refactor
2025-06-04 13:37:58 +05:30
Prateek ShouryaandGitHub 2e822b38e4 [WEB-4240] chore: bump local db version to 1.3 #7154 2025-06-04 13:01:29 +05:30
JayashTripathyandGitHub e570fe404f [WEB-4182] Fix work item links error messages (#7122)
* fix: backend error message toast when getting error

* fix: toast in small screens
2025-06-03 22:18:26 +05:30
Aaryan KhandelwalandGitHub 48b613ae66 [WIKI-410] chore: editor translation files #7156 2025-06-03 22:13:56 +05:30
sriram veeraghantaandGitHub 461e099bbc release: v0.26.0 #6962 2025-04-28 18:24:37 +05:30
sriram veeraghantaandGitHub 45e25ce18b release: v0.25.3 #6788 2025-03-21 17:26:55 +05:30
sriram veeraghantaandGitHub 4d88dbaf49 release: v0.25.2 (#6736) 2025-03-11 16:01:20 +05:30
+4 e61ff879c4 release: v0.25.1
* fix: issue activity for project id validation (#6668)

* fix: work item attachment count mutation (#6670)

* updated the action to modify the release build assets (#6669)

* feat: russian translation (#6666)

* chore: ru translation updated (#6672)

* fix: state drop down refactor

* fix: intake work item creation refactor

* fix: cleanup for deprecated functions

* fix: date range picker on cycles and modules list (#6676)

* fix: Handled workspace switcher closing on click

* fix: replaced date range picker with date picker at some places

* chore: add common translation keys (#6688)

* chore: add missing translation keys

* chore: add russian translation keys

* fix: issue activity task (#6689)

* changed github workflow action ubuntu version to `ubuntu-22.04` (#6683)

* chore: update russian translation (#6682)

* chore: update russian translation

* chore: rename issues to work items in russian translation

* [PE-275] chore: editor line spacing variables (#6678)

* chore: variable editor line spacing

* chore: variable list spacing

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* [WEB-3475] fix: cycle dates dropdown (#6690)

* fix: Handled workspace switcher closing on click

* fix: Cycle date picker

* fix: Made onSelect optional in range range component

* fix: module date picker (#6691)

* fix: Handled workspace switcher closing on click

* fix: reverted module date picker changes

* chore: extended sidebar improvement (#6693)

* feat: italian translations (#6692)

* Create translations.json - ITALIAN translation (#6667)

* chore: italian translation updated

* feat: italian translation added

* fix: module end date translation

---------

Co-authored-by: Nicolas Bossi <nicolasbossi@gmail.com>
Co-authored-by: gakshita <akshitagoyal1516@gmail.com>

* fix: attachment item created by (#6695)

* fix: module flicker issue on property updation (#6699)

* [WEB-3477] fix: mutation issue on moving work items for a manually ended cycle (#6696)

* fix: package version update

* fix: esbuild version fix

* fix: package license repliation

* [WEB-3488] improvement: assignee validation for work item creation (#6701)

* fix: work item assignee update validation (#6704)

---------

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

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