Compare commits

...

17 Commits

Author SHA1 Message Date
Henry
ea92dcd2ed feat: add basic white labelling (#146) 2025-08-15 15:20:07 +01:00
Henry
d51600aba8 refactor: improve card layout and scrollbar styling (#143) 2025-08-14 22:10:45 +01:00
Henry
c3ed2e5e7f fix: missing translation for editor placeholder (#142) 2025-08-14 21:36:43 +01:00
Henry
13b10471ec feat: card checklists (#141)
* feat: setup checklist schema

* feat: scaffold checklist router

* feat: add create new checklist modal

* feat: create new checklist item

* feat: toggle checklist item completed state

* feat: update checklist name

* feat: delete checklist

* feat: show checklists progress on cards

* feat: tweak light mode styling

* feat: focus item form on creation of new checklist

* feat: tweak new checklist item form styling

* feat: add checklist activity

* feat: show checklist progress on public board page

* feat: display checklist items on public card modal

* chore: add translations
2025-08-14 19:59:36 +01:00
Cyber
80d1c5f805 fix: check if POSTGRES_URL env variable is set before starting the migration script (if external db) (#139) 2025-08-14 19:58:26 +01:00
Ridham Khandar
8079345546 feat: tagging members in description (#140)
* feat: tagging members in description

* chore: tiptap mention package

* feat: avatar before names in members dropdown
2025-08-14 19:57:35 +01:00
Ridham Khandar
4a67afdd3a fix: add new created label to selected label (#136) 2025-08-09 20:35:29 +01:00
Cyber
d555546782 feat: add pglite support (#138) 2025-08-09 20:32:13 +01:00
Henry
e53ec868a5 fix: only use cloud mailer client if creds exist 2025-08-04 11:29:17 +01:00
Henry
872a5fad28 feat: public board enhancements (#134)
* feat: hide dropdown on comments for public boards

* feat: fix height of modal and add scrollable content area

* feat: add view only badge to public boards

* feat: prevent card title from overlapping close button

* chore: gen translations
2025-08-01 22:03:38 +01:00
Henry
400b94a5f2 feat: enhance member invite error handling and add invite more toggle (#133) 2025-07-30 22:24:05 +01:00
Henry
638b8b6b79 fix: update public board lookup to filter by workspace slug and show not found message (#131)
* fix: update public board lookup to filter by workspace slug

* feat: show not found message for public boards

* chore: update lang
2025-07-28 22:59:28 +01:00
Ridham Khandar
ca1dd92ab4 fix: disable create list and label button when input is empty (#130) 2025-07-28 21:49:24 +01:00
Ridham Khandar
f8827da73a fix: creating a new label when adding a new card resets all fields (#119)
* fix: creating a new label when adding a new card resets all fields

* fix: persist form data and modal stacking without breaking the animations
2025-07-28 21:49:13 +01:00
Henry
babaa9f1b1 feat: setup plunk for transactional/marketing emails 2025-07-28 07:09:30 +01:00
Henry
0cefd17484 fix: prevent member dropdown from being cutoff (#127) 2025-07-26 21:34:05 +01:00
Henry
974a5ff0f3 fix: update board slug availability check to be unique to workspace (#126) 2025-07-26 21:18:26 +01:00
76 changed files with 9681 additions and 1224 deletions

View File

@@ -3,6 +3,8 @@
# Required environment variables
NEXT_PUBLIC_BASE_URL= # e.g. https://kan.bn
BETTER_AUTH_SECRET= # Random 32+ char string (can gen with: openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
# Fill if you want to use an external database
POSTGRES_URL= # e.g. postgresql://kan:your_password@your_host:5432/kan
# Only required if deploying from compose

3
.gitignore vendored
View File

@@ -50,3 +50,6 @@ docker-compose.override.yml
# runtime env
__ENV.js
i18n.cache
# pgdata
/apps/web/pgdata

View File

@@ -138,36 +138,37 @@ pnpm dev
## Environment Variables 🔐
| Variable | Description | Required | Example |
| -------------------------------- | -------------------------------------------------------- | ------------------ | --------------------------------------------- |
| `POSTGRES_URL` | PostgreSQL connection URL | Yes | `postgres://user:pass@localhost:5432/db` |
| `EMAIL_FROM` | Sender email address | For Email | `"Kan <hello@mail.kan.bn>"` |
| `SMTP_HOST` | SMTP server hostname | For Email | `smtp.resend.com` |
| `SMTP_PORT` | SMTP server port | For Email | `465` |
| `SMTP_USER` | SMTP username/email | No | `resend` |
| `SMTP_PASSWORD` | SMTP password/token | No | `re_xxxx` |
| `SMTP_SECURE` | Use secure SMTP connection (defaults to true if not set) | For Email | `true` |
| `NEXT_PUBLIC_BASE_URL` | Base URL of your installation | Yes | `http://localhost:3000` |
| `BETTER_AUTH_SECRET` | Auth encryption secret | Yes | Random 32+ char string |
| `BETTER_AUTH_TRUSTED_ORIGINS` | Allowed callback origins | No | `http://localhost:3000,http://localhost:3001` |
| `GOOGLE_CLIENT_ID` | Google OAuth client ID | For Google login | `xxx.apps.googleusercontent.com` |
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | For Google login | `xxx` |
| `DISCORD_CLIENT_ID` | Discord OAuth client ID | For Discord login | `xxx` |
| `DISCORD_CLIENT_SECRET` | Discord OAuth client secret | For Discord login | `xxx` |
| `GITHUB_CLIENT_ID` | GitHub OAuth client ID | For GitHub login | `xxx` |
| `GITHUB_CLIENT_SECRET` | GitHub OAuth client secret | For GitHub login | `xxx` |
| `TRELLO_APP_API_KEY` | Trello app API key | For Trello import | `xxx` |
| `TRELLO_APP_API_SECRET` | Trello app API secret | For Trello import | `xxx` |
| `S3_REGION` | S3 storage region | For file uploads | `WEUR` |
| `S3_ENDPOINT` | S3 endpoint URL | For file uploads | `https://xxx.r2.cloudflarestorage.com` |
| `S3_ACCESS_KEY_ID` | S3 access key | For file uploads | `xxx` |
| `S3_SECRET_ACCESS_KEY` | S3 secret key | For file uploads | `xxx` |
| `S3_FORCE_PATH_STYLE` | Use path-style URLs for S3 | For file uploads | `true` |
| `NEXT_PUBLIC_STORAGE_URL` | Storage service URL | For file uploads | `https://storage.kanbn.com` |
| `NEXT_PUBLIC_STORAGE_DOMAIN` | Storage domain name | For file uploads | `kanbn.com` |
| `NEXT_PUBLIC_AVATAR_BUCKET_NAME` | S3 bucket name for avatars | For file uploads | `avatars` |
| `NEXT_PUBLIC_ALLOW_CREDENTIALS` | Allow email & password login | For authentication | `true` |
| `NEXT_PUBLIC_DISABLE_SIGN_UP` | Disable sign up | For authentication | `false` |
| Variable | Description | Required | Example |
| ----------------------------------------- | -------------------------------------------------------- | ------------------------ | --------------------------------------------- |
| `POSTGRES_URL` | PostgreSQL connection URL | To use external database | `postgres://user:pass@localhost:5432/db` |
| `EMAIL_FROM` | Sender email address | For Email | `"Kan <hello@mail.kan.bn>"` |
| `SMTP_HOST` | SMTP server hostname | For Email | `smtp.resend.com` |
| `SMTP_PORT` | SMTP server port | For Email | `465` |
| `SMTP_USER` | SMTP username/email | No | `resend` |
| `SMTP_PASSWORD` | SMTP password/token | No | `re_xxxx` |
| `SMTP_SECURE` | Use secure SMTP connection (defaults to true if not set) | For Email | `true` |
| `NEXT_PUBLIC_BASE_URL` | Base URL of your installation | Yes | `http://localhost:3000` |
| `BETTER_AUTH_SECRET` | Auth encryption secret | Yes | Random 32+ char string |
| `BETTER_AUTH_TRUSTED_ORIGINS` | Allowed callback origins | No | `http://localhost:3000,http://localhost:3001` |
| `GOOGLE_CLIENT_ID` | Google OAuth client ID | For Google login | `xxx.apps.googleusercontent.com` |
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | For Google login | `xxx` |
| `DISCORD_CLIENT_ID` | Discord OAuth client ID | For Discord login | `xxx` |
| `DISCORD_CLIENT_SECRET` | Discord OAuth client secret | For Discord login | `xxx` |
| `GITHUB_CLIENT_ID` | GitHub OAuth client ID | For GitHub login | `xxx` |
| `GITHUB_CLIENT_SECRET` | GitHub OAuth client secret | For GitHub login | `xxx` |
| `TRELLO_APP_API_KEY` | Trello app API key | For Trello import | `xxx` |
| `TRELLO_APP_API_SECRET` | Trello app API secret | For Trello import | `xxx` |
| `S3_REGION` | S3 storage region | For file uploads | `WEUR` |
| `S3_ENDPOINT` | S3 endpoint URL | For file uploads | `https://xxx.r2.cloudflarestorage.com` |
| `S3_ACCESS_KEY_ID` | S3 access key | For file uploads | `xxx` |
| `S3_SECRET_ACCESS_KEY` | S3 secret key | For file uploads | `xxx` |
| `S3_FORCE_PATH_STYLE` | Use path-style URLs for S3 | For file uploads | `true` |
| `NEXT_PUBLIC_STORAGE_URL` | Storage service URL | For file uploads | `https://storage.kanbn.com` |
| `NEXT_PUBLIC_STORAGE_DOMAIN` | Storage domain name | For file uploads | `kanbn.com` |
| `NEXT_PUBLIC_AVATAR_BUCKET_NAME` | S3 bucket name for avatars | For file uploads | `avatars` |
| `NEXT_PUBLIC_ALLOW_CREDENTIALS` | Allow email & password login | For authentication | `true` |
| `NEXT_PUBLIC_DISABLE_SIGN_UP` | Disable sign up | For authentication | `false` |
| `NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY` | Hide “Powered by kan.bn” on public boards (self-host) | For white labelling | `true` |
See `.env.example` for a complete list of supported environment variables.

View File

@@ -81,5 +81,4 @@ ARG PORT=3000
ENV PORT=${PORT}
EXPOSE ${PORT}
CMD ["sh", "-c", "cd /app && pnpm db:migrate && cd /app/apps/web && pnpm start"]
CMD ["sh", "-c", "if [ -n \"$POSTGRES_URL\" ]; then cd /app && pnpm db:migrate && cd /app/apps/web; fi && pnpm start"]

View File

@@ -13,12 +13,19 @@ checksums:
Activity%20logs/singular: 8b1f0bb96a905646ecfad1cfdfa42168
Add%20a%20card/singular: 9d80becc4fe65a74e0004e4fd290735d
Add%20a%20comment.../singular: c28cfccc0cc69a2a2487e37eb9988028
Add%20description...%20(type%20'%2F'%20to%20open%20commands)/singular: df99ea599521682e9ad0a90716955dc6
Add%20an%20item.../singular: f8e7993980420698700b07f56b3a38af
Add%20checklist/singular: 05d73200b56cfea75fb3a0f94c9bfb74
Add%20description...%20(type%20'%2F'%20to%20open%20commands%20or%20'%40'%20to%20mention)/singular: c6eb35a796dd7ac0e3dbc66244b71248
Add%20details.../singular: 2f42547fd5d199f173aa7a88c8a9b19a
Add%20label/singular: 0be732d46df263265935fda342097a08
Add%20member/singular: 11979625770516ca287e929381778e02
added%20a%20checklist/singular: 44304f4a5ef3a46378eea243b5d2df79
added%20a%20checklist%20item/singular: 20bc0330dba5a9ec978e04bec174365a
added%20a%20label%20to%20the%20card/singular: 04c5e4ee9b7b7e77ef089d2ac473d235
added%20a%20member%20to%20the%20card/singular: 95495e770062e2fd590333d56e6308d2
added%20label%20%3C0%3E%7Blabel%7D%3C%2F0%3E/singular: 1caba6b6bca4c6fa1ab173d241059dc2
added%20checklist%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: fb6a852c50c1629f47c6f155fc93fdee
added%20checklist%20item%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: bdd202da20b1fffbec21792c5453f90c
added%20label%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: b32be052b3d57de0c9120fa7f9fc86ee
Admin%20roles/singular: 32a5d78073b9bb9a246773afba8831df
All%20seats%20are%20free%20during%20the%20beta%20until%20Sept%201st!/singular: 015cec3899cf99e37c3cf231d432e31d
All%20systems%20operational/singular: ee943a4046b09e6334cceeea9fda2bfc
@@ -31,9 +38,11 @@ checksums:
Are%20you%20sure%20want%20to%20remove%20%7BentityLabel%7D%3F/singular: f9b67026c5ef86ac4d5944380d571961
Are%20you%20sure%20you%20want%20to%20delete%20the%20workspace%20%7B0%7D%3F/singular: 27ba2854202dd77c51e78c64e5c93946
Are%20you%20sure%20you%20want%20to%20delete%20this%20card%3F/singular: 131a5a7cccca7be8b32c987d9fa0b0c0
Are%20you%20sure%20you%20want%20to%20delete%20this%20checklist%3F/singular: 7c0f540603474b1a30462dda087fd2f0
Are%20you%20sure%20you%20want%20to%20delete%20this%20comment%3F/singular: 55fe6f1b52b71b91b2a079cfda3cc7fe
Are%20you%20sure%20you%20want%20to%20delete%20this%20label%3F/singular: 037fbaf1d04356937e6afb574714f51f
Are%20you%20sure%20you%20want%20to%20delete%20your%20account%3F/singular: 428b74d781063224c3e63980e1094ec3
assigned%20%3C0%3E%7BmemberName%7D%3C%2F0%3E%20to%20the%20card/singular: 4fb1c419a78a0f010a3e5790801551fc
assigned%20%3C0%3E%7B0%7D%3C%2F0%3E%20to%20the%20card/singular: 9df6d89fa83706123dedd8cba2243d04
Awaiting%20Customer/singular: 122c108794f321c10e05a08f09b11457
Backlog/singular: 35919fd5b10ad212839a75e72829fe4e
Basic%20Kanban/singular: e46ebc406749486f3907436970d4d6c7
@@ -60,6 +69,7 @@ checksums:
Card%20title/singular: 7c34f59f4005e6cb3a6ff546ea0b96e3
Change%20the%20language%20of%20the%20app./singular: fb20db145e28ed44aba89c146ded7bfc
Check%20your%20inbox/singular: e9a430fcd298def74212238df0f680d6
Checklist%20name/singular: 5eb5de823f7ca5a4d97bb41e6a3f675a
Clear%20filters/singular: 8f40ab5af527e4b190da94e7b6221379
Click%20on%20the%20link%20we've%20sent%20to%20%7BmagicLinkRecipient%7D%20to%20sign%20in./singular: 210b6ff8727f976182ec3f29ea3c7667
Code%20Review/singular: a2da6c2339301e7c3ddf068c4ff9f7e8
@@ -69,6 +79,8 @@ checksums:
Company/singular: 9e36d11fa82d1bd4563eef2abc4fcfba
Complete/singular: d3e36d723de0cfdea51c5b4171d30b4d
Complete%20control%20and%20ownership%3A/singular: 0d8b682ba873272217425ccfc96aa9cd
completed%20a%20checklist%20item/singular: 757b04c6c80cc927e1c597c0ad4fda33
completed%20checklist%20item%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: 71ec18acf051fc909a48a07ca3578673
Confirm%20URL%20change/singular: e14b4ce081d1ef6194b37e1814e97b39
Connect%20Trello/singular: 4440a0b9e387ef7136e3958e7a089213
Connect%20your%20favorite%20tools%20to%20streamline%20your%20workflow./singular: 033c5dcdb059ccb634aef7fe63f2f45d
@@ -82,6 +94,7 @@ checksums:
Create%20another/singular: 2de8a82a416eb78c0462aa36278edc9a
Create%20board/singular: 155b62818bfab0e34f0089e1b34a32f3
Create%20card/singular: 32792935dd5837a9433909b04021c22b
Create%20checklist/singular: 5cbca15a7004558c4e6d381f83a34da2
Create%20label/singular: c64e8180fa956f005edc1fd9b8e65abb
Create%20list/singular: 858c3d514aa95765ec82114393199144
Create%20new%20board/singular: cbb87b1f4cc46b336ad9510c9f19407c
@@ -102,6 +115,10 @@ checksums:
Delete%20comment/singular: a3dae0c9ddbc73677aad9812e3dffd69
Delete%20list/singular: b694f58e4c973d8797a78e746b550b1f
Delete%20workspace/singular: 9017c821209528d9b20d40a488854cdf
deleted%20a%20checklist/singular: c54f922dc6c238c9141c700cb3180baf
deleted%20a%20checklist%20item/singular: 2b7a861600102ac5c271dd25c1df261a
deleted%20checklist%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: 60f9107197e756487d3f95a1083846b5
deleted%20checklist%20item%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: 8b7e270f612f9e91bda7f5cfb853d843
Design/singular: 991b75727b6784c1a063a7462b76186d
Disconnect%20Trello/singular: a2dd737d133887d34504728c7e7f05e3
Discuss%20and%20collaborate%20on%20cards./singular: f2e87816709f602130831ead50bba457
@@ -127,8 +144,10 @@ checksums:
Enter%20your%20name/singular: cd95fbdd0533f2c2e8edf9d9bd9aa8df
Enter%20your%20password/singular: ea4fdd034522dead21bae0c0abb52eae
Error%20deleting%20account/singular: d42965a9bc9e5ec4ed57890268924643
Error%20deleting%20label/singular: 94387e3a45ec768ae7715701ae00136e
Error%20deleting%20workspace/singular: 0aec9bd8170bc84f5ea5c9a47c52ed26
Error%20disconnecting%20Trello/singular: a8766853864eddc480113778a708871b
Error%20inviting%20member/singular: 91553b606a6ebed3534cd2e5635ce004
Error%20updating%20display%20name/singular: 0d7979a25d290123ed8b8906bf468f1a
Error%20updating%20profile%20image/singular: 462a56a6e574abc9e13cb48f1266adad
Error%20updating%20workspace%20description/singular: 7a9fd7c69b90ab848ee1f1d4a4995381
@@ -185,7 +204,9 @@ checksums:
Individuals/singular: 83bfc81f20e60556c44ec80c7d0603ec
Integrations/singular: 0ccce343287704cd90150c32e2fcad36
Interviewing/singular: 4ccdcdc784547e925077c3297bddee95
Invalid%20email%20address/singular: b2d9f25626f2d15c7c63e0281bccc247
Invite/singular: 181884cea804cbde665f160811ee7ad0
Invite%20another/singular: acb543563dab7edbcf46060a53ade3a3
Invite%20member/singular: ade922db1be6b26bc979565ce5de2bc7
Junior/singular: ed1bd2c59a824fdcdd56fc8a0660fe9f
Kanban%20is%20better%20with%20a%20team.%20Perfect%20for%20small%20and%20growing%20teams%20looking%20to%20collaborate./singular: a77bee43046b260797c8936ad23e9223
@@ -206,17 +227,20 @@ checksums:
Long-term/singular: 51cb6f0b112250c5a091ca5f0866904b
Low%20Priority/singular: 509b5cb7c1e8a185e0aa6d38c64c90e8
magic%20link/singular: db9d17a5bd98bba8aa0d8068c68579f3
marked%20a%20checklist%20item%20as%20incomplete/singular: 35d0822f65971b97774b962561b02649
marked%20checklist%20item%20%3C0%3E%7B0%7D%3C%2F0%3E%20as%20incomplete/singular: 4c38799ff25321ea25017bf4cd8e2e4f
Medium%20Priority/singular: 1f527cd6d1ed602930bcaa303f503b51
Members/singular: 0932e80cba1e3e0a7f52bb67ff31da32
Members%20%7C%20%7B0%7D/singular: a29e3e9f1076acd178c417d047584e88
Monthly/singular: 818f1192e32bb855597f930d3e78806e
moved%20the%20card%20from%20%3C0%3E%7BfromList%7D%3C%2F0%3E%20to%3C1%3E%7BtoList%7D%3C%2F1%3E/singular: c2d0d72ad97510344b51154ef15060e0
moved%20the%20card%20from%20%3C0%3E%7B0%7D%3C%2F0%3E%20to%3C1%3E%7B1%7D%3C%2F1%3E/singular: 11edf0427766c26db541d46379dc3c16
moved%20the%20card%20to%20another%20list/singular: 2e8c41bfafb42fa299ce56e4300205ff
Name/singular: 9368b5a047572b6051f334af5aa76819
Need%20help%3F/singular: 04e7322f2d3ffb2d73ff2f64b71637c8
New/singular: 126d036fae5fb6b629728ecb97e6195b
New%20board/singular: 63f4e979e29a7fc2f5c09ff91fa75966
New%20card/singular: a33f6219a756127f91c2523cfe845a19
New%20checklist/singular: 58252b71e9693ae0f4d2d0b72108a569
New%20import/singular: e8fddfe15dd170277df919e5d37efd47
New%20label/singular: 890040da52a88c16c249a4ac78980f81
New%20list/singular: 146c2e642b11056a0b07ed65733e4178
@@ -263,7 +287,10 @@ checksums:
Remove%20member/singular: 1d77c2ca3768e486fd1fb5df30cb60d9
removed%20a%20label%20from%20the%20card/singular: f83ec18850a2a8a3f8242a740a04df06
removed%20a%20member%20from%20the%20card/singular: 0bd7561bb79358fde3d595c3e60a315a
removed%20label%20%3C0%3E%7Blabel%7D%3C%2F0%3E/singular: 1e7ebc1866136352bdc3cd90afc083f0
removed%20label%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: 7e0936b15831e65754588b8512f62fcb
renamed%20a%20checklist/singular: 4d208de1857740e63469007c5b8b491a
renamed%20checklist%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: e95d119ef65b0af6c0a96bef182be671
renamed%20checklist%20item%20to%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: 50f55f71f9245890afee434d7adc2140
Research/singular: 3368e9638d1619babd6df9fad592274f
Resolution/singular: 6d8bd9e1bd7dae5ae38c93061d32990e
Resources/singular: ec7fb05ed963bb6781a35782b3475502
@@ -308,6 +335,7 @@ checksums:
Theme/singular: 21fe00b7a518089576fb83c08631107a
They%20won't%20be%20able%20to%20access%20this%20workspace./singular: 93b740350fe3430319fbca85349e41d9
This%20action%20can't%20be%20undone./singular: cb222ff89715d8c971e8c25d121e1dbd
This%20board%20is%20private%20or%20does%20not%20exist/singular: a217ff3f04463b4df8c86adb6f83c6bc
This%20board%20URL%20has%20already%20been%20taken/singular: 1d8b40332a031b5b77a3658e48dd51ca
This%20will%20result%20in%20the%20permanent%20deletion%20of%20all%20data%20associated%20with%20this%20workspace./singular: a31141558af793635c1ddd2fa0a33499
This%20will%20result%20in%20the%20permanent%20deletion%20of%20all%20data%20associated%20with%20your%20account./singular: b49224632bd6c3b7f5e462912aeb1081
@@ -318,22 +346,28 @@ checksums:
Trello%20disconnected/singular: 54b24a3e6c9a7eedd8c8d1060ab1175d
Trello%20imports/singular: 6827eca403faa8f89891d17827e72af9
Triaging/singular: 1d40799fcae53a8a27688fdae2a48dee
Unable%20to%20add%20checklist%20item/singular: 4c4c3eaaf10b348b39ae97eb5dc455df
Unable%20to%20add%20comment/singular: 49bb435880817434698f31a6069564d6
Unable%20to%20create%20card/singular: 90112ea12ec6fa42097a6e022ae048a4
Unable%20to%20create%20checklist/singular: 94eed122e42e0951cd08b9ec62f5eeb6
Unable%20to%20create%20list/singular: 7fbbf8314f8d08a4123c7daef09fed05
Unable%20to%20create%20workspace/singular: c1c949477e121d73ed6683eddf9cd874
Unable%20to%20delete%20card/singular: 50ff06aa1265fe464f776aba33f1bef9
Unable%20to%20delete%20checklist/singular: d7067e2c009f4919f0cca77df3c228ee
Unable%20to%20delete%20checklist%20item/singular: a74144593b625e4d8d7ecf734462c60d
Unable%20to%20delete%20comment/singular: 550198b2c87f06726a843c79c1026ed9
Unable%20to%20remove%20member/singular: 39025a0c53818438829603d213baef06
Unable%20to%20send%20feedback/singular: 656c93265d7e2bef1245b17d85f85ae5
Unable%20to%20update%20board%20URL/singular: 080746884059142358b58d9a44ff7d93
Unable%20to%20update%20board%20visibility/singular: a76a21d561b8943e9276e027a1e3f70d
Unable%20to%20update%20card/singular: eca6002e57af8ec97324eae64e3b5731
Unable%20to%20update%20checklist/singular: 44c10f49f448909ceca481ed9b4b07e7
Unable%20to%20update%20checklist%20item/singular: 5c86b3523531c30d58856e3ecde0cb55
Unable%20to%20update%20comment/singular: 05e798ed6b0f3fee41f3e8832eb699ea
Unable%20to%20update%20labels/singular: dca2bdc3dcf74bc9d95e05156039a291
Unable%20to%20update%20list/singular: 14aa802f91b9b4c05236c8c75afb33da
Unable%20to%20update%20members/singular: 9a851a6b0c75ee16d25cf2ff4b67b255
unassigned%20%3C0%3E%7BmemberName%7D%3C%2F0%3E%20from%20the%20card/singular: 105d4eeb32044126b8dde243bad11d56
unassigned%20%3C0%3E%7B0%7D%3C%2F0%3E%20from%20the%20card/singular: b683cc07092348c1e75dba40b1262b85
unassigned%20themselves%20from%20the%20card/singular: 27c6f293c562af6a44348d7f00036d30
Unlimited%20activity%20log/singular: 8c993de94cda0deac19ba14ecafce6a5
Unlimited%20boards/singular: cb4fd858137ce825c2f1904d142d8042
@@ -343,9 +377,10 @@ checksums:
Unlimited%20lists/singular: 5c418701b27b9acbc5dd199719007362
Update/singular: 079fc039262fd31b10532929685c2d1b
Update%20label/singular: 97880b503dfe956941c5f23025a1c102
updated%20a%20checklist%20item/singular: 198e1d1d503f4b69b752dd84d0cc0e9d
updated%20the%20description/singular: 70fd604b6dc957a75be90ea77572d76d
updated%20the%20title/singular: 98cf8f12923ec1a58e767e3a40863b21
updated%20the%20title%20to%20%3C0%3E%7BtoTitle%7D%3C%2F0%3E/singular: 70cdf681ec2b6ef8e14397bc1f93f945
updated%20the%20title%20to%20%3C0%3E%7B0%7D%3C%2F0%3E/singular: aca196d729401d8adc5c8ca9d558840e
Upgrade/singular: 63c3b52882e0d779859307d672c178c2
Urgent/singular: e79afcd4398e52e10e7e8243a7eecabe
URL%20can%20only%20contain%20letters%2C%20numbers%2C%20and%20hyphens/singular: 875d37d2d920f98ef60a314eda8a5d9a
@@ -353,11 +388,14 @@ checksums:
URL%20must%20be%20at%20least%203%20characters%20long/singular: 1b74ac9ec61fd34b16530ed599009ed1
Use%20template/singular: 33f0ca0aa8f4b0de99aad267a61a094d
User/singular: 61073457a5c3901084b557d065f876be
User%20is%20already%20a%20member%20of%20this%20workspace/singular: bc246386f0975f750f2133222bb4735e
Video/singular: 8050c90e4289b105a0780f0fdda6ff66
View%20and%20manage%20your%20API%20keys./singular: 88e23ee79056846dd9ce63ceee690549
View%20and%20manage%20your%20billing%20and%20subscription./singular: 7469502543aac6f02149813546d1cb38
View%20docs/singular: 43ce54f27bb1e3f1880b56e03ab38871
View%20only/singular: 21f5f7ba3b49050c4f406113479d6a7a
View%20our%20roadmap./singular: 1dd7ec80e1635951b1c90d592d1d07dc
View%20workspace/singular: c9e4b789330b08c18ca8db7044409e5f
Visibility/singular: 1ff412f8fcde0f460acbf11153649646
We%20are%20using%20the%20%3C0%3EAGPL-3.0%20license%3C%2F0%3E./singular: 08c5ddd938a2a70f61d684e41e18697a
We're%20just%20getting%20started.%20/singular: 2dfa121e0ff1914e08068308a81973c0

View File

@@ -33,6 +33,7 @@
"@tailwindcss/typography": "^0.5.16",
"@tanstack/react-query": "catalog:",
"@tiptap/extension-link": "^2.22.2",
"@tiptap/extension-mention": "^3.0.9",
"@tiptap/extension-placeholder": "^2.14.0",
"@tiptap/pm": "^2.14.0",
"@tiptap/react": "^2.14.0",

View File

@@ -5,7 +5,7 @@ import LoadingSpinner from "./LoadingSpinner";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "danger" | "ghost";
size?: "sm" | "md" | "lg";
size?: "xs" | "sm" | "md" | "lg";
isLoading?: boolean;
iconLeft?: React.ReactNode;
iconRight?: React.ReactNode;
@@ -28,6 +28,7 @@ const Button = ({
}: ButtonProps) => {
const classes = twMerge(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none",
size === "xs" && "text-xs px-2 py-1",
size === "sm" && "text-xs",
size === "lg" && "py-[0.65rem]",
fullWidth && "w-full",

View File

@@ -86,8 +86,10 @@ export default function CheckboxDropdown({
</label>
{handleEdit && (
<button
type="button"
className="invisible ml-auto group-hover:visible"
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleEdit(item.key);
}}
@@ -100,8 +102,12 @@ export default function CheckboxDropdown({
))}
{handleCreate && (
<button
type="button"
className="flex w-full items-center rounded-[5px] p-2 px-2 text-[12px] text-dark-900 hover:bg-light-200 dark:hover:bg-dark-300"
onClick={() => handleCreate()}
onClick={(e) => {
e.preventDefault();
handleCreate();
}}
>
<HiMiniPlus size={20} className="pr-1.5" />
{createNewItemLabel}

View File

@@ -0,0 +1,58 @@
import { twMerge } from "tailwind-merge";
interface CircularProgressProps {
progress: number; // 0-100
size?: "sm" | "md" | "lg";
className?: string;
}
const CircularProgress = ({
progress,
size = "md",
className,
}: CircularProgressProps) => {
const radius = 40;
const circumference = 2 * Math.PI * radius;
const strokeDashoffset = circumference - (progress / 100) * circumference;
return (
<div className={twMerge("relative", className)}>
<svg
className={twMerge(
"-rotate-90 transform",
size === "sm" && "h-4 w-4",
size === "md" && "h-5 w-5",
size === "lg" && "h-8 w-8",
)}
viewBox="0 0 100 100"
>
<circle
cx="50"
cy="50"
r={radius}
fill="none"
stroke="currentColor"
strokeWidth="14"
className="text-light-300 dark:text-dark-400"
/>
<circle
cx="50"
cy="50"
r={radius}
fill="none"
stroke="currentColor"
strokeWidth="14"
strokeDasharray={circumference}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
className={twMerge(
"transition-all duration-300 ease-in-out",
progress === 100 ? "text-green-500" : "text-blue-500",
)}
/>
</svg>
</div>
);
};
export default CircularProgress;

View File

@@ -1,3 +1,5 @@
import { t } from "@lingui/core/macro";
import Button from "~/components/Button";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
@@ -10,21 +12,23 @@ export function DeleteLabelConfirmation({
labelPublicId: string;
refetch: () => void;
}) {
const { closeModal } = useModal();
const { closeModal, closeModals } = useModal();
const { showPopup } = usePopup();
const deleteLabelMutation = api.label.delete.useMutation({
onSuccess: () => refetch(),
onSuccess: () => {
refetch();
closeModals(2);
},
onError: () =>
showPopup({
header: "Error deleting label",
message: "Please try again later, or contact customer support.",
header: t`Error deleting label`,
message: t`Please try again later, or contact customer support.`,
icon: "error",
}),
});
const handleDeleteLabel = () => {
closeModal();
deleteLabelMutation.mutate({
labelPublicId,
});
@@ -34,17 +38,17 @@ export function DeleteLabelConfirmation({
<div className="p-5">
<div className="flex w-full flex-col justify-between pb-4">
<h2 className="text-md pb-4 font-medium text-neutral-900 dark:text-dark-1000">
Are you sure you want to delete this label?
{t`Are you sure you want to delete this label?`}
</h2>
<p className="text-sm font-medium text-light-900 dark:text-dark-900">
{"This action can't be undone."}
{t`This action can't be undone.`}
</p>
</div>
<div className="mt-5 flex justify-end space-x-2 sm:mt-6">
<Button variant="secondary" onClick={() => closeModal()}>
Cancel
{t`Cancel`}
</Button>
<Button onClick={handleDeleteLabel}>Delete</Button>
<Button onClick={handleDeleteLabel}>{t`Delete`}</Button>
</div>
</div>
);

View File

@@ -17,7 +17,13 @@ import {
} from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import Suggestion from "@tiptap/suggestion";
import { forwardRef, useImperativeHandle, useRef, useState } from "react";
import {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react";
import {
HiH1,
HiH2,
@@ -33,6 +39,9 @@ import {
} from "react-icons/hi2";
import { twMerge } from "tailwind-merge";
import tippy from "tippy.js";
import Mention from "@tiptap/extension-mention";
import Avatar from "./Avatar";
import { getAvatarUrl } from "~/utils/helpers";
declare module "@tiptap/core" {
interface Commands<ReturnType> {
@@ -68,6 +77,16 @@ export interface RenderSuggestionsProps {
command: (item: SlashCommandItem) => void;
}
export type WorkspaceMember = {
publicId: string;
user: {
id: string;
name: string | null;
image: string | null;
} | null;
email: string;
};
const CommandsList = forwardRef<
{ onKeyDown: (props: SuggestionKeyDownProps) => boolean },
{
@@ -181,6 +200,125 @@ const RenderSuggestions = () => {
};
};
type MentionItem = { id: string; label: string; image: string | null };
const MentionList = forwardRef<
{ onKeyDown: (props: SuggestionKeyDownProps) => boolean },
{
items: MentionItem[];
command: (item: MentionItem) => void;
}
>(({ items, command }, ref) => {
const [selectedIndex, setSelectedIndex] = useState(0);
useImperativeHandle(ref, () => ({
onKeyDown: ({ event }: SuggestionKeyDownProps) => {
if (event.key === "ArrowUp") {
setSelectedIndex((selectedIndex + items.length - 1) % items.length);
return true;
}
if (event.key === "ArrowDown") {
setSelectedIndex((selectedIndex + 1) % items.length);
return true;
}
if (event.key === "Enter") {
const item = items[selectedIndex];
if (item) {
command(item);
}
return true;
}
return false;
},
}));
return (
<div className="w-56 rounded-md border-[1px] border-light-200 bg-light-50 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-dark-500 dark:bg-dark-200">
<div className="max-h-[350px] overflow-y-auto p-1 scrollbar-thin scrollbar-thumb-light-200 dark:scrollbar-thumb-dark-300">
{items.length > 0 ? items.map((item, index) => (
<button
key={item.id}
onClick={() => command(item)}
className={twMerge(
"group flex w-full items-center rounded-[5px] p-2 hover:bg-light-200 dark:hover:bg-dark-300",
index === selectedIndex && "bg-light-200 dark:bg-dark-300",
)}
>
<Avatar
size="xs"
name={item.label}
imageUrl={
item.image ? getAvatarUrl(item.image) : undefined
}
email={item.label}
/>
<span className="ml-3 text-[12px] font-medium text-dark-900 dark:text-dark-1000">
{item.label}
</span>
</button>
)) : (
<div className="flex items-center justify-start p-2">
<span className="text-dark-900 text-[12px] dark:text-dark-1000">No results</span>
</div>
)}
</div>
</div>
);
});
MentionList.displayName = "MentionList";
const renderMentionSuggestions = () => {
let reactRenderer: ReactRenderer;
let popup: TippyInstance[];
return {
onStart: (props: any) => {
reactRenderer = new ReactRenderer(MentionList, {
props,
editor: props.editor,
});
if (!props.clientRect) return;
popup = tippy("body", {
getReferenceClientRect: props.clientRect,
appendTo: () => document.body,
content: reactRenderer.element,
showOnCreate: true,
interactive: true,
trigger: "manual",
placement: "bottom-start",
});
},
onUpdate(props: any) {
reactRenderer.updateProps(props);
if (!props.clientRect) return;
popup[0]?.setProps({ getReferenceClientRect: props.clientRect });
},
onKeyDown(props: SuggestionKeyDownProps) {
if (props.event.key === "Escape") {
popup[0]?.hide();
return true;
}
return (
(
reactRenderer.ref as {
onKeyDown?: (props: SuggestionKeyDownProps) => boolean;
}
).onKeyDown?.(props) ?? false
);
},
onExit() {
popup[0]?.destroy();
reactRenderer.destroy();
},
};
};
const SlashCommands = Extension.create<SlashCommandsOptions>({
name: "slash-commands",
addOptions() {
@@ -284,11 +422,13 @@ export default function Editor({
onChange,
onBlur,
readOnly = false,
workspaceMembers,
}: {
content: string | null;
onChange?: (value: string) => void;
onBlur?: () => void;
readOnly?: boolean;
workspaceMembers: WorkspaceMember[];
}) {
const containerRef = useRef<HTMLDivElement>(null);
@@ -299,7 +439,7 @@ export default function Editor({
Placeholder.configure({
placeholder: readOnly
? ""
: t`Add description... (type '/' to open commands)`,
: t`Add description... (type '/' to open commands or '@' to mention)`,
}),
Link.configure({
openOnClick: true,
@@ -320,6 +460,38 @@ export default function Editor({
char: "/",
},
}),
Mention.configure({
HTMLAttributes: {
class: 'mention',
},
suggestion: {
char: "@",
items: ({ query }: { query: string }) => {
const all: MentionItem[] = workspaceMembers.map((member: WorkspaceMember) => ({
id: member.publicId,
label: member?.user?.name ?? member.email,
image: member?.user?.image ?? null,
}));
const q = query.toLowerCase();
return all.filter((u) => u.label.toLowerCase().includes(q));
},
command: ({ editor, range, props }: any) => {
const mentionHTML = `<span data-type="mention" data-id="${props.id}" data-label="${props.label}">@${props.label}</span>&nbsp;`;
editor
.chain()
.focus()
.deleteRange(range)
.insertContent(mentionHTML)
.focus()
.run();
},
render: renderMentionSuggestions,
},
renderText({ options, node }) {
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`;
},
}),
],
content,
onUpdate: ({ editor }) => onChange?.(editor.getHTML()),
@@ -343,9 +515,19 @@ export default function Editor({
editable: !readOnly,
injectCSS: false,
},
[content],
[], // creating the editor only once
);
// this will sync external content changes without re-creating the editor instance
useEffect(() => {
if (!editor) return;
const currentHTML = editor.getHTML();
const safeContent = content ?? "";
if (safeContent !== currentHTML) {
editor.commands.setContent(safeContent, false);
}
}, [content, editor]);
return (
<div ref={containerRef}>
<style jsx global>{`
@@ -358,11 +540,19 @@ export default function Editor({
.tiptap p {
margin: 0 0 1rem 0 !important;
}
.tiptap .mention {
background-color: rgba(59, 130, 246, 0.1);
border-radius: 0.25rem;
padding: 0.125rem 0.25rem;
color: rgb(59, 130, 246);
text-decoration: none;
font-weight: 500;
}
`}</style>
{!readOnly && editor && <EditorBubbleMenu editor={editor} />}
<EditorContent
editor={editor}
className="prose dark:prose-invert prose-sm max-w-none overflow-y-auto [&_blockquote]:!text-xs [&_h1]:!text-lg [&_h2]:!text-base [&_h3]:!text-sm [&_ol]:!text-xs [&_p.is-empty::before]:text-light-900 [&_p.is-empty::before]:dark:text-dark-800 [&_p]:!text-sm [&_p]:text-black [&_p]:dark:text-white [&_ul]:!text-xs"
className="prose dark:prose-invert prose-sm max-w-none overflow-y-auto [&_blockquote]:!text-xs [&_h1]:!text-lg [&_h2]:!text-base [&_h3]:!text-sm [&_ol]:!text-xs [&_p.is-empty::before]:text-light-900 [&_p.is-empty::before]:dark:text-dark-800 [&_p]:!text-sm [&_p]:text-light-950 [&_p]:dark:text-dark-950 [&_ul]:!text-xs"
/>
</div>
);

View File

@@ -32,7 +32,7 @@ export function LabelForm({
refetch: () => void;
isEdit?: boolean;
}) {
const { closeModal, entityId, openModal } = useModal();
const { closeModal, entityId, openModal, setModalState } = useModal();
const label = api.label.byPublicId.useQuery(
{
@@ -57,18 +57,23 @@ export function LabelForm({
const isCreateAnotherEnabled = watch("isCreateAnotherEnabled");
const createLabel = api.label.create.useMutation({
onSuccess: () => {
onSuccess: (newLabel) => {
const currentColourIndex = colours.findIndex(
(c) => c.code === watch("colour").code,
);
try {
refetch();
if (!isCreateAnotherEnabled) closeModal();
reset({
name: "",
colour: colours[(currentColourIndex + 1) % colours.length],
isCreateAnotherEnabled,
});
setModalState("NEW_LABEL_CREATED", newLabel.publicId);
if (!isCreateAnotherEnabled) {
closeModal();
} else {
const newFormState = {
name: "",
colour: colours[(currentColourIndex + 1) % colours.length],
isCreateAnotherEnabled,
};
reset(newFormState);
}
} catch (e) {
console.log(e);
}
@@ -79,10 +84,6 @@ export function LabelForm({
onSuccess: () => {
refetch();
closeModal();
reset({
name: "",
colour: colours[0],
});
},
});
@@ -220,6 +221,7 @@ export function LabelForm({
<div className="space-x-2">
{isEdit && (
<Button
type="button"
variant="secondary"
onClick={() => openModal("DELETE_LABEL", entityId)}
>
@@ -229,6 +231,9 @@ export function LabelForm({
<Button
type="submit"
isLoading={updateLabel.isPending || createLabel.isPending}
disabled={
!watch("name")
}
>
{isEdit ? t`Update label` : t`Create label`}
</Button>

View File

@@ -7,15 +7,19 @@ interface Props {
children: React.ReactNode;
modalSize?: "sm" | "md" | "lg";
positionFromTop?: "sm" | "md" | "lg";
isVisible?: boolean;
}
const Modal: React.FC<Props> = ({
children,
modalSize = "sm",
positionFromTop = "md",
isVisible,
}) => {
const { isOpen, closeModal } = useModal();
const shouldShow = isVisible !== undefined ? isVisible : isOpen;
const modalSizeMap = {
sm: "max-w-[400px]",
md: "max-w-[550px]",
@@ -29,7 +33,7 @@ const Modal: React.FC<Props> = ({
};
return (
<Transition.Root show={isOpen} as={Fragment}>
<Transition.Root show={shouldShow} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}

View File

@@ -24,7 +24,7 @@ export const env = createEnv({
s.split(",").every((l) => z.string().url().safeParse(l).success),
)
.optional(),
POSTGRES_URL: z.string().url(),
POSTGRES_URL: z.string().url().optional().or(z.literal("")),
TRELLO_APP_API_KEY: z.string().optional(),
TRELLO_APP_SECRET: z.string().optional(),
STRIPE_SECRET_KEY: z.string().optional(),
@@ -101,6 +101,13 @@ export const env = createEnv({
(s) => !s || s.toLowerCase() === "true" || s.toLowerCase() === "false",
)
.optional(),
NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY: z
.string()
.transform((s) => (s === "" ? undefined : s))
.refine(
(s) => !s || s.toLowerCase() === "true" || s.toLowerCase() === "false",
)
.optional(),
},
/**
* Destructure all variables from `process.env` to make sure they aren't tree-shaken away.
@@ -119,6 +126,8 @@ export const env = createEnv({
NEXT_PUBLIC_DISABLE_SIGN_UP: process.env.NEXT_PUBLIC_DISABLE_SIGN_UP,
NEXT_PUBLIC_USE_STANDALONE_OUTPUT:
process.env.NEXT_PUBLIC_USE_STANDALONE_OUTPUT,
NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY:
process.env.NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY,
},
skipValidation:
!!process.env.CI || process.env.npm_lifecycle_event === "lint",

View File

@@ -0,0 +1,48 @@
import { useEffect } from "react";
import { useModal } from "~/providers/modal";
interface UseModalFormStateOptions<T> {
modalType: string;
initialValues: T;
resetOnClose?: boolean;
}
export function useModalFormState<T extends Record<string, any>>({
modalType,
initialValues,
resetOnClose = false,
}: UseModalFormStateOptions<T>) {
const { modalContentType, isOpen, getModalState, setModalState, clearModalState } = useModal();
const isCurrentModal = modalContentType === modalType;
const savedState = getModalState(modalType) as T | undefined;
// get current form state (using the saved values if available, otherwise the initial values)
const formState = savedState || initialValues;
const saveFormState = (state: Partial<T>) => {
if (!isCurrentModal) return;
const currentState = getModalState(modalType) || initialValues;
const newState = { ...currentState, ...state };
setModalState(modalType, newState);
};
const clearFormState = () => {
clearModalState(modalType);
};
useEffect(() => {
if (resetOnClose && !isOpen && savedState) {
clearModalState(modalType);
}
}, [isOpen, resetOnClose, savedState, modalType, clearModalState]);
return {
formState,
saveFormState,
clearFormState,
isCurrentModal,
hasSavedState: !!savedState,
};
}

View File

@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: src/views/card/components/Comment.tsx:121
#: src/views/card/components/Comment.tsx:123
msgid " (edited)"
msgstr " (bearbeitet)"
@@ -23,12 +23,12 @@ msgstr ", oder besuche unsere"
#. placeholder {0}: card?.title ?? "Card"
#. placeholder {1}: board?.name ?? "Board"
#: src/views/card/index.tsx:189
#: src/views/card/index.tsx:214
msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:432
msgid "{0} labels"
msgstr "{0} Labels"
@@ -48,8 +48,8 @@ msgstr "Eine leistungsstarke, flexible Kanban-App, die dir hilft, Arbeit zu orga
msgid "Account deleted"
msgstr "Konto gelöscht"
#: src/views/public/board/CardModal.tsx:104
#: src/views/card/index.tsx:256
#: src/views/public/board/CardModal.tsx:140
#: src/views/card/index.tsx:288
msgid "Activity"
msgstr "Aktivität"
@@ -62,35 +62,66 @@ msgid "Add a card"
msgstr "Karte hinzufügen"
#: src/views/card/components/NewCommentForm.tsx:57
#: src/views/card/components/Comment.tsx:144
#: src/views/card/components/Comment.tsx:146
msgid "Add a comment..."
msgstr "Kommentar hinzufügen..."
#: src/components/Editor.tsx:302
msgid "Add description... (type '/' to open commands)"
msgstr "Beschreibung hinzufügen... (tippe '/' um Befehle zu öffnen)"
#: src/views/card/components/NewChecklistItemForm.tsx:140
msgid "Add an item..."
msgstr "Element hinzufügen..."
#: src/views/card/components/Dropdown.tsx:18
msgid "Add checklist"
msgstr "Checkliste hinzufügen"
#: src/components/Editor.tsx:442
msgid "Add description... (type '/' to open commands or '@' to mention)"
msgstr "Beschreibung hinzufügen... (tippe '/' um Befehle zu öffnen oder '@' um jemanden zu erwähnen)"
#: src/views/card/components/ChecklistItemRow.tsx:172
msgid "Add details..."
msgstr "Details hinzufügen..."
#: src/views/card/components/LabelSelector.tsx:110
#: src/views/card/components/LabelSelector.tsx:118
msgid "Add label"
msgstr "Label hinzufügen"
#: src/views/members/components/InviteMemberForm.tsx:50
#: src/views/members/components/InviteMemberForm.tsx:85
#: src/views/card/components/MemberSelector.tsx:130
msgid "Add member"
msgstr "Mitglied hinzufügen"
#: src/views/card/components/ActivityList.tsx:56
#: src/views/card/components/ActivityList.tsx:69
msgid "added a checklist"
msgstr "hat eine Checkliste hinzugefügt"
#: src/views/card/components/ActivityList.tsx:72
msgid "added a checklist item"
msgstr "hat ein Checklistenelement hinzugefügt"
#: src/views/card/components/ActivityList.tsx:65
msgid "added a label to the card"
msgstr "hat ein Label zur Karte hinzugefügt"
#: src/views/card/components/ActivityList.tsx:58
#: src/views/card/components/ActivityList.tsx:67
msgid "added a member to the card"
msgstr "hat ein Mitglied zur Karte hinzugefügt"
#: src/views/card/components/ActivityList.tsx:110
msgid "added label <0>{label}</0>"
msgstr "hat Label <0>{label}</0> hinzugefügt"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:146
msgid "added checklist <0>{0}</0>"
msgstr "hat Checkliste <0>{0}</0> hinzugefügt"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:170
msgid "added checklist item <0>{0}</0>"
msgstr "hat Checklistenelement <0>{0}</0> hinzugefügt"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:130
msgid "added label <0>{0}</0>"
msgstr "hat Label <0>{0}</0> hinzugefügt"
#: src/views/home/components/Pricing.tsx:55
msgid "Admin roles"
@@ -141,17 +172,26 @@ msgstr "Bist du sicher, dass du den Workspace {0} löschen möchtest?"
msgid "Are you sure you want to delete this card?"
msgstr "Bist du sicher, dass du diese Karte löschen möchtest?"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:55
msgid "Are you sure you want to delete this checklist?"
msgstr "Sind Sie sicher, dass Sie diese Checkliste löschen möchten?"
#: src/views/card/components/DeleteCommentConfirmation.tsx:65
msgid "Are you sure you want to delete this comment?"
msgstr "Bist du sicher, dass du diesen Kommentar löschen möchtest?"
#: src/components/DeleteLabelConfirmation.tsx:41
msgid "Are you sure you want to delete this label?"
msgstr "Sind Sie sicher, dass Sie dieses Label löschen möchten?"
#: src/views/settings/components/DeleteAccountConfirmation.tsx:54
msgid "Are you sure you want to delete your account?"
msgstr "Bist du sicher, dass du dein Konto löschen möchtest?"
#: src/views/card/components/ActivityList.tsx:92
msgid "assigned <0>{memberName}</0> to the card"
msgstr "hat <0>{memberName}</0> der Karte zugewiesen"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:110
msgid "assigned <0>{0}</0> to the card"
msgstr "hat <0>{0}</0> der Karte zugewiesen"
#: src/views/boards/components/TemplateBoards.tsx:46
msgid "Awaiting Customer"
@@ -178,7 +218,7 @@ msgstr "Abrechnungsportal"
msgid "Blog Post"
msgstr "Blogbeitrag"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Board"
msgstr "Board"
@@ -190,7 +230,8 @@ msgstr "Der Name des Boards darf 100 Zeichen nicht überschreiten"
msgid "Board name is required"
msgstr "Boardname ist erforderlich"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:143
#: src/views/board/index.tsx:357
msgid "Board not found"
msgstr "Board nicht gefunden"
@@ -202,7 +243,7 @@ msgstr "Board-URL darf nur Buchstaben, Zahlen und Bindestriche enthalten"
msgid "Board URL cannot exceed 60 characters"
msgstr "Board-URL darf 60 Zeichen nicht überschreiten"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "Board-URL in die Zwischenablage kopiert"
@@ -245,16 +286,18 @@ msgstr "Fehlerbericht"
#: src/views/settings/components/CustomURLConfirmation.tsx:50
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
#: src/views/card/components/DeleteCardConfirmation.tsx:86
#: src/views/card/components/Comment.tsx:156
#: src/views/card/components/Comment.tsx:158
#: src/components/DeleteLabelConfirmation.tsx:49
msgid "Cancel"
msgstr "Abbrechen"
#: src/views/card/index.tsx:234
#: src/views/card/index.tsx:259
msgid "Card not found"
msgstr "Karte nicht gefunden"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:307
msgid "Card title"
msgstr "Kartentitel"
@@ -267,6 +310,10 @@ msgstr "Ändere die Sprache der App."
msgid "Check your inbox"
msgstr "Überprüfe deinen Posteingang"
#: src/views/card/components/NewChecklistForm.tsx:118
msgid "Checklist name"
msgstr "Checklistenname"
#: src/views/board/components/Filters.tsx:155
msgid "Clear filters"
msgstr "Filter löschen"
@@ -304,6 +351,15 @@ msgstr "Abgeschlossen"
msgid "Complete control and ownership:"
msgstr "Vollständige Kontrolle und Eigentum:"
#: src/views/card/components/ActivityList.tsx:74
msgid "completed a checklist item"
msgstr "hat ein Checklistenelement abgeschlossen"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:187
msgid "completed checklist item <0>{0}</0>"
msgstr "hat Checklistenelement <0>{0}</0> abgeschlossen"
#: src/views/settings/components/CustomURLConfirmation.tsx:42
msgid "Confirm URL change"
msgstr "URL-Änderung bestätigen"
@@ -347,8 +403,8 @@ msgid "Control who can view and edit your boards."
msgstr "Kontrolliere, wer deine Boards ansehen und bearbeiten kann."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/components/LabelForm.tsx:212
#: src/views/board/components/NewCardForm.tsx:458
#: src/components/LabelForm.tsx:213
msgid "Create another"
msgstr "Weitere erstellen"
@@ -356,15 +412,19 @@ msgstr "Weitere erstellen"
msgid "Create board"
msgstr "Board erstellen"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:468
msgid "Create card"
msgstr "Karte erstellen"
#: src/components/LabelForm.tsx:233
#: src/views/card/components/NewChecklistForm.tsx:134
msgid "Create checklist"
msgstr "Checkliste erstellen"
#: src/components/LabelForm.tsx:238
msgid "Create label"
msgstr "Label erstellen"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Liste erstellen"
@@ -377,11 +437,11 @@ msgid "Create new key"
msgstr "Neuen Schlüssel erstellen"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:394
msgid "Create new label"
msgstr "Neues Label erstellen"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:425
msgid "Create new list"
msgstr "Neue Liste erstellen"
@@ -390,7 +450,7 @@ msgstr "Neue Liste erstellen"
msgid "Create workspace"
msgstr "Arbeitsbereich erstellen"
#: src/views/card/components/ActivityList.tsx:52
#: src/views/card/components/ActivityList.tsx:61
msgid "created the card"
msgstr "hat die Karte erstellt"
@@ -415,8 +475,10 @@ msgid "Dark"
msgstr "Dunkel"
#: src/views/card/components/DeleteCommentConfirmation.tsx:82
#: src/views/card/components/DeleteChecklistConfirmation.tsx:66
#: src/views/card/components/DeleteCardConfirmation.tsx:92
#: src/components/LabelForm.tsx:226
#: src/components/LabelForm.tsx:228
#: src/components/DeleteLabelConfirmation.tsx:51
msgid "Delete"
msgstr "Löschen"
@@ -430,11 +492,11 @@ msgstr "Konto löschen"
msgid "Delete board"
msgstr "Board löschen"
#: src/views/card/components/Dropdown.tsx:14
#: src/views/card/components/Dropdown.tsx:25
msgid "Delete card"
msgstr "Karte löschen"
#: src/views/card/components/Comment.tsx:89
#: src/views/card/components/Comment.tsx:91
msgid "Delete comment"
msgstr "Kommentar löschen"
@@ -448,6 +510,24 @@ msgstr "Liste löschen"
msgid "Delete workspace"
msgstr "Workspace löschen"
#: src/views/card/components/ActivityList.tsx:71
msgid "deleted a checklist"
msgstr "hat eine Checkliste gelöscht"
#: src/views/card/components/ActivityList.tsx:76
msgid "deleted a checklist item"
msgstr "hat ein Checklistenelement gelöscht"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:162
msgid "deleted checklist <0>{0}</0>"
msgstr "hat Checkliste <0>{0}</0> gelöscht"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:205
msgid "deleted checklist item <0>{0}</0>"
msgstr "hat Checklistenelement <0>{0}</0> gelöscht"
#: src/views/boards/components/TemplateBoards.tsx:32
msgid "Design"
msgstr "Design"
@@ -510,11 +590,11 @@ msgstr "Erledigt"
msgid "Edit board URL"
msgstr "Board-URL bearbeiten"
#: src/views/card/components/Comment.tsx:80
#: src/views/card/components/Comment.tsx:82
msgid "Edit comment"
msgstr "Kommentar bearbeiten"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "Edit label"
msgstr "Label bearbeiten"
@@ -530,7 +610,7 @@ msgstr "Bearbeitung"
msgid "email"
msgstr "E-Mail"
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/InviteMemberForm.tsx:99
msgid "Email"
msgstr "E-Mail"
@@ -554,6 +634,10 @@ msgstr "Gib dein Passwort ein"
msgid "Error deleting account"
msgstr "Fehler beim Löschen des Kontos"
#: src/components/DeleteLabelConfirmation.tsx:25
msgid "Error deleting label"
msgstr "Fehler beim Löschen des Labels"
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:45
msgid "Error deleting workspace"
msgstr "Fehler beim Löschen des Arbeitsbereichs"
@@ -562,6 +646,11 @@ msgstr "Fehler beim Löschen des Arbeitsbereichs"
msgid "Error disconnecting Trello"
msgstr "Fehler beim Trennen von Trello"
#: src/views/members/components/InviteMemberForm.tsx:57
#: src/views/members/components/InviteMemberForm.tsx:63
msgid "Error inviting member"
msgstr "Fehler beim Einladen des Mitglieds"
#: src/views/settings/components/UpdateDisplayNameForm.tsx:54
msgid "Error updating display name"
msgstr "Fehler beim Aktualisieren des Anzeigenamens"
@@ -676,7 +765,7 @@ msgstr "Loslegen"
msgid "Get started by creating a new board"
msgstr "Beginne, indem du ein neues Board erstellst"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:417
msgid "Get started by creating a new list"
msgstr "Beginne mit dem Erstellen einer neuen Liste"
@@ -799,11 +888,19 @@ msgstr "Integrationen"
msgid "Interviewing"
msgstr "Vorstellungsgespräch"
#: src/views/members/components/InviteMemberForm.tsx:26
msgid "Invalid email address"
msgstr "Ungültige E-Mail-Adresse"
#: src/views/members/index.tsx:169
msgid "Invite"
msgstr "Einladen"
#: src/views/members/components/InviteMemberForm.tsx:82
#: src/views/members/components/InviteMemberForm.tsx:113
msgid "Invite another"
msgstr "Weitere Person einladen"
#: src/views/members/components/InviteMemberForm.tsx:123
#: src/views/card/components/MemberSelector.tsx:112
msgid "Invite member"
msgstr "Mitglied einladen"
@@ -825,8 +922,8 @@ msgstr "Kanban neu gedacht"
msgid "Keep in mind that this action is irreversible."
msgstr "Beachten Sie, dass diese Aktion nicht rückgängig gemacht werden kann."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/card/index.tsx:118
#: src/views/board/components/NewCardForm.tsx:398
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Labels"
@@ -855,11 +952,11 @@ msgstr "Lizenz"
msgid "Light"
msgstr "Hell"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Link kopiert"
#: src/views/card/index.tsx:105
#: src/views/card/index.tsx:110
msgid "List"
msgstr "Liste"
@@ -887,13 +984,22 @@ msgstr "Niedrige Priorität"
msgid "magic link"
msgstr "Magic Link"
#: src/views/card/components/ActivityList.tsx:75
msgid "marked a checklist item as incomplete"
msgstr "hat ein Checklistenelement als unvollständig markiert"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:196
msgid "marked checklist item <0>{0}</0> as incomplete"
msgstr "hat Checklistenelement <0>{0}</0> als unvollständig markiert"
#: src/views/boards/components/TemplateBoards.tsx:17
msgid "Medium Priority"
msgstr "Mittlere Priorität"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/card/index.tsx:126
#: src/views/board/components/NewCardForm.tsx:357
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -908,16 +1014,18 @@ msgstr "Mitglieder | {0}"
msgid "Monthly"
msgstr "Monatlich"
#: src/views/card/components/ActivityList.tsx:81
msgid "moved the card from <0>{fromList}</0> to<1>{toList}</1>"
msgstr "hat die Karte von <0>{fromList}</0> nach <1>{toList}</1> verschoben"
#. placeholder {0}: truncate(fromList)
#. placeholder {1}: truncate(toList)
#: src/views/card/components/ActivityList.tsx:98
msgid "moved the card from <0>{0}</0> to<1>{1}</1>"
msgstr "hat die Karte von <0>{0}</0> nach <1>{1}</1> verschoben"
#: src/views/card/components/ActivityList.tsx:55
#: src/views/card/components/ActivityList.tsx:64
msgid "moved the card to another list"
msgstr "hat die Karte in eine andere Liste verschoben"
#: src/views/boards/components/NewBoardForm.tsx:99
#: src/components/LabelForm.tsx:134
#: src/components/LabelForm.tsx:135
msgid "Name"
msgstr "Name"
@@ -933,19 +1041,23 @@ msgstr "Neu"
msgid "New board"
msgstr "Neues Board"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:290
msgid "New card"
msgstr "Neue Karte"
#: src/views/card/components/NewChecklistForm.tsx:102
msgid "New checklist"
msgstr "Neue Checkliste"
#: src/views/boards/components/ImportBoardsForm.tsx:322
msgid "New import"
msgstr "Neuer Import"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "New label"
msgstr "Neues Label"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:394
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "Neue Liste"
@@ -978,7 +1090,7 @@ msgstr "Keine Boards gefunden"
msgid "No credit card required"
msgstr "Keine Kreditkarte erforderlich"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:414
msgid "No lists"
msgstr "Keine Listen"
@@ -1059,24 +1171,32 @@ msgstr "Bitte wähle eine Datei zum Hochladen aus."
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
#: src/views/settings/components/Avatar.tsx:40
#: src/views/settings/components/Avatar.tsx:98
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
#: src/views/card/index.tsx:159
#: src/views/card/index.tsx:173
#: src/views/card/components/NewCommentForm.tsx:31
#: src/views/card/components/NewChecklistItemForm.tsx:89
#: src/views/card/components/NewChecklistForm.tsx:70
#: src/views/card/components/MemberSelector.tsx:80
#: src/views/card/components/ListSelector.tsx:53
#: src/views/card/components/LabelSelector.tsx:73
#: src/views/card/components/DeleteCommentConfirmation.tsx:45
#: src/views/card/components/DeleteChecklistConfirmation.tsx:37
#: src/views/card/components/DeleteCardConfirmation.tsx:52
#: src/views/card/components/Comment.tsx:62
#: src/views/card/components/Comment.tsx:64
#: src/views/card/components/ChecklistNameInput.tsx:46
#: src/views/card/components/ChecklistItemRow.tsx:60
#: src/views/card/components/ChecklistItemRow.tsx:88
#: src/views/boards/components/ImportBoardsForm.tsx:205
#: src/views/board/index.tsx:135
#: src/views/board/index.tsx:191
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:190
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:174
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
#: src/components/DeleteLabelConfirmation.tsx:26
msgid "Please try again later, or contact customer support."
msgstr "Bitte versuche es später noch einmal oder kontaktiere den Kundensupport."
@@ -1142,17 +1262,32 @@ msgstr "Entfernen"
msgid "Remove member"
msgstr "Mitglied entfernen"
#: src/views/card/components/ActivityList.tsx:57
#: src/views/card/components/ActivityList.tsx:66
msgid "removed a label from the card"
msgstr "hat ein Label von der Karte entfernt"
#: src/views/card/components/ActivityList.tsx:59
#: src/views/card/components/ActivityList.tsx:68
msgid "removed a member from the card"
msgstr "hat ein Mitglied von der Karte entfernt"
#: src/views/card/components/ActivityList.tsx:118
msgid "removed label <0>{label}</0>"
msgstr "hat Label <0>{label}</0> entfernt"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:138
msgid "removed label <0>{0}</0>"
msgstr "hat Label <0>{0}</0> entfernt"
#: src/views/card/components/ActivityList.tsx:70
msgid "renamed a checklist"
msgstr "hat eine Checkliste umbenannt"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:154
msgid "renamed checklist <0>{0}</0>"
msgstr "hat Checkliste <0>{0}</0> umbenannt"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:178
msgid "renamed checklist item to <0>{0}</0>"
msgstr "hat Checklistenelement in <0>{0}</0> umbenannt"
#: src/views/boards/components/TemplateBoards.tsx:76
msgid "Research"
@@ -1187,7 +1322,7 @@ msgstr "Rolle"
msgid "Run on your own infrastructure"
msgstr "Auf eigener Infrastruktur betreiben"
#: src/views/card/components/Comment.tsx:163
#: src/views/card/components/Comment.tsx:165
msgid "Save"
msgstr "Speichern"
@@ -1215,7 +1350,7 @@ msgstr "Selbst hosten"
msgid "Self host with Github"
msgstr "Selbst hosten mit Github"
#: src/views/card/components/ActivityList.tsx:89
#: src/views/card/components/ActivityList.tsx:107
msgid "self-assigned the card"
msgstr "hat sich selbst die Karte zugewiesen"
@@ -1334,10 +1469,16 @@ msgid "They won't be able to access this workspace."
msgstr "Sie werden keinen Zugriff mehr auf diesen Workspace haben."
#: src/views/card/components/DeleteCommentConfirmation.tsx:68
#: src/views/card/components/DeleteChecklistConfirmation.tsx:58
#: src/views/card/components/DeleteCardConfirmation.tsx:78
#: src/components/DeleteLabelConfirmation.tsx:44
msgid "This action can't be undone."
msgstr "Diese Aktion kann nicht rückgängig gemacht werden."
#: src/views/public/board/index.tsx:146
msgid "This board is private or does not exist"
msgstr "Dieses Board ist privat oder existiert nicht"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "Diese Board-URL ist bereits vergeben"
@@ -1379,14 +1520,22 @@ msgstr "Trello-importe"
msgid "Triaging"
msgstr "Priorisierung"
#: src/views/card/components/NewChecklistItemForm.tsx:88
msgid "Unable to add checklist item"
msgstr "Checklistenelement kann nicht hinzugefügt werden"
#: src/views/card/components/NewCommentForm.tsx:30
msgid "Unable to add comment"
msgstr "Kommentar konnte nicht hinzugefügt werden"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:171
msgid "Unable to create card"
msgstr "Karte konnte nicht erstellt werden"
#: src/views/card/components/NewChecklistForm.tsx:69
msgid "Unable to create checklist"
msgstr "Checkliste kann nicht erstellt werden"
#: src/views/board/components/NewListForm.tsx:77
msgid "Unable to create list"
msgstr "Liste konnte nicht erstellt werden"
@@ -1399,6 +1548,14 @@ msgstr "Workspace konnte nicht erstellt werden"
msgid "Unable to delete card"
msgstr "Karte konnte nicht gelöscht werden"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:36
msgid "Unable to delete checklist"
msgstr "Checkliste kann nicht gelöscht werden"
#: src/views/card/components/ChecklistItemRow.tsx:87
msgid "Unable to delete checklist item"
msgstr "Checklistenelement kann nicht gelöscht werden"
#: src/views/card/components/DeleteCommentConfirmation.tsx:44
msgid "Unable to delete comment"
msgstr "Kommentar konnte nicht gelöscht werden"
@@ -1419,12 +1576,20 @@ msgstr "Board-URL kann nicht aktualisiert werden"
msgid "Unable to update board visibility"
msgstr "Board-Sichtbarkeit konnte nicht aktualisiert werden"
#: src/views/card/index.tsx:158
#: src/views/board/index.tsx:190
#: src/views/card/index.tsx:172
#: src/views/board/index.tsx:189
msgid "Unable to update card"
msgstr "Karte konnte nicht aktualisiert werden"
#: src/views/card/components/Comment.tsx:61
#: src/views/card/components/ChecklistNameInput.tsx:45
msgid "Unable to update checklist"
msgstr "Checkliste kann nicht aktualisiert werden"
#: src/views/card/components/ChecklistItemRow.tsx:59
msgid "Unable to update checklist item"
msgstr "Checklistenelement kann nicht aktualisiert werden"
#: src/views/card/components/Comment.tsx:63
msgid "Unable to update comment"
msgstr "Kommentar konnte nicht aktualisiert werden"
@@ -1433,7 +1598,7 @@ msgid "Unable to update labels"
msgstr "Labels konnten nicht aktualisiert werden"
#: src/views/card/components/ListSelector.tsx:52
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:133
msgid "Unable to update list"
msgstr "Liste konnte nicht aktualisiert werden"
@@ -1441,11 +1606,12 @@ msgstr "Liste konnte nicht aktualisiert werden"
msgid "Unable to update members"
msgstr "Mitglieder konnten nicht aktualisiert werden"
#: src/views/card/components/ActivityList.tsx:102
msgid "unassigned <0>{memberName}</0> from the card"
msgstr "<0>{memberName}</0> von der Karte entfernt"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:121
msgid "unassigned <0>{0}</0> from the card"
msgstr "hat <0>{0}</0> von der Karte entfernt"
#: src/views/card/components/ActivityList.tsx:99
#: src/views/card/components/ActivityList.tsx:118
msgid "unassigned themselves from the card"
msgstr "hat sich selbst von der Karte entfernt"
@@ -1477,25 +1643,30 @@ msgstr "Unbegrenzte listen"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Aktualisieren"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:238
msgid "Update label"
msgstr "Label aktualisieren"
#: src/views/card/components/ActivityList.tsx:54
#: src/views/card/components/ActivityList.tsx:73
msgid "updated a checklist item"
msgstr "hat ein Checklistenelement aktualisiert"
#: src/views/card/components/ActivityList.tsx:63
msgid "updated the description"
msgstr "hat die Beschreibung aktualisiert"
#: src/views/card/components/ActivityList.tsx:53
#: src/views/card/components/ActivityList.tsx:62
msgid "updated the title"
msgstr "hat den Titel aktualisiert"
#: src/views/card/components/ActivityList.tsx:73
msgid "updated the title to <0>{toTitle}</0>"
msgstr "hat den Titel zu <0>{toTitle}</0> aktualisiert"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:90
msgid "updated the title to <0>{0}</0>"
msgstr "hat den Titel in <0>{0}</0> geändert"
#: src/views/settings/components/CustomURLConfirmation.tsx:52
msgid "Upgrade"
@@ -1526,6 +1697,10 @@ msgstr "Vorlage verwenden"
msgid "User"
msgstr "Benutzer"
#: src/views/members/components/InviteMemberForm.tsx:58
msgid "User is already a member of this workspace"
msgstr "Benutzer ist bereits Mitglied dieses Arbeitsbereichs"
#: src/views/boards/components/TemplateBoards.tsx:37
msgid "Video"
msgstr "Video"
@@ -1542,10 +1717,18 @@ msgstr "Verwalte deine Abrechnung und dein Abonnement."
msgid "View docs"
msgstr "Dokumentation ansehen"
#: src/views/public/board/index.tsx:120
msgid "View only"
msgstr "Nur ansehen"
#: src/views/home/components/Features.tsx:147
msgid "View our roadmap."
msgstr "Sieh dir unsere roadmap an."
#: src/views/public/board/index.tsx:149
msgid "View workspace"
msgstr "Workspace anzeigen"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Sichtbarkeit"
@@ -1578,7 +1761,7 @@ msgstr "Als Trello 2011 auf den markt kam, beeindruckte es alle mit seiner sorgf
msgid "Why make an open source Trello?"
msgstr "Warum ein open source Trello entwickeln?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Workspace"
msgstr "Arbeitsbereich"

File diff suppressed because one or more lines are too long

View File

@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: src/views/card/components/Comment.tsx:121
#: src/views/card/components/Comment.tsx:123
msgid " (edited)"
msgstr " (edited)"
@@ -23,12 +23,12 @@ msgstr ", or see our"
#. placeholder {0}: card?.title ?? "Card"
#. placeholder {1}: board?.name ?? "Board"
#: src/views/card/index.tsx:189
#: src/views/card/index.tsx:214
msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:432
msgid "{0} labels"
msgstr "{0} labels"
@@ -60,8 +60,8 @@ msgstr "A powerful, flexible kanban app that helps you organise work, track prog
msgid "Account deleted"
msgstr "Account deleted"
#: src/views/public/board/CardModal.tsx:104
#: src/views/card/index.tsx:256
#: src/views/public/board/CardModal.tsx:140
#: src/views/card/index.tsx:288
msgid "Activity"
msgstr "Activity"
@@ -74,35 +74,74 @@ msgid "Add a card"
msgstr "Add a card"
#: src/views/card/components/NewCommentForm.tsx:57
#: src/views/card/components/Comment.tsx:144
#: src/views/card/components/Comment.tsx:146
msgid "Add a comment..."
msgstr "Add a comment..."
#: src/components/Editor.tsx:302
msgid "Add description... (type '/' to open commands)"
msgstr "Add description... (type '/' to open commands)"
#: src/views/card/components/NewChecklistItemForm.tsx:140
msgid "Add an item..."
msgstr "Add an item..."
#: src/views/card/components/Dropdown.tsx:18
msgid "Add checklist"
msgstr "Add checklist"
#: src/components/Editor.tsx:442
msgid "Add description... (type '/' to open commands or '@' to mention)"
msgstr "Add description... (type '/' to open commands or '@' to mention)"
#: src/components/Editor.tsx:308
#~ msgid "Add description... (type '/' to open commands)"
#~ msgstr "Add description... (type '/' to open commands)"
#: src/views/card/components/ChecklistItemRow.tsx:172
msgid "Add details..."
msgstr "Add details..."
#: src/views/card/components/LabelSelector.tsx:110
#: src/views/card/components/LabelSelector.tsx:118
msgid "Add label"
msgstr "Add label"
#: src/views/members/components/InviteMemberForm.tsx:50
#: src/views/members/components/InviteMemberForm.tsx:85
#: src/views/card/components/MemberSelector.tsx:130
msgid "Add member"
msgstr "Add member"
#: src/views/card/components/ActivityList.tsx:56
#: src/views/card/components/ActivityList.tsx:69
msgid "added a checklist"
msgstr "added a checklist"
#: src/views/card/components/ActivityList.tsx:72
msgid "added a checklist item"
msgstr "added a checklist item"
#: src/views/card/components/ActivityList.tsx:65
msgid "added a label to the card"
msgstr "added a label to the card"
#: src/views/card/components/ActivityList.tsx:58
#: src/views/card/components/ActivityList.tsx:67
msgid "added a member to the card"
msgstr "added a member to the card"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:146
msgid "added checklist <0>{0}</0>"
msgstr "added checklist <0>{0}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:170
msgid "added checklist item <0>{0}</0>"
msgstr "added checklist item <0>{0}</0>"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:130
msgid "added label <0>{0}</0>"
msgstr "added label <0>{0}</0>"
#: src/views/card/components/ActivityList.tsx:110
msgid "added label <0>{label}</0>"
msgstr "added label <0>{label}</0>"
#~ msgid "added label <0>{label}</0>"
#~ msgstr "added label <0>{label}</0>"
#: src/views/home/components/Pricing.tsx:55
msgid "Admin roles"
@@ -157,17 +196,30 @@ msgstr "Are you sure you want to delete the workspace {0}?"
msgid "Are you sure you want to delete this card?"
msgstr "Are you sure you want to delete this card?"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:55
msgid "Are you sure you want to delete this checklist?"
msgstr "Are you sure you want to delete this checklist?"
#: src/views/card/components/DeleteCommentConfirmation.tsx:65
msgid "Are you sure you want to delete this comment?"
msgstr "Are you sure you want to delete this comment?"
#: src/components/DeleteLabelConfirmation.tsx:41
msgid "Are you sure you want to delete this label?"
msgstr "Are you sure you want to delete this label?"
#: src/views/settings/components/DeleteAccountConfirmation.tsx:54
msgid "Are you sure you want to delete your account?"
msgstr "Are you sure you want to delete your account?"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:110
msgid "assigned <0>{0}</0> to the card"
msgstr "assigned <0>{0}</0> to the card"
#: src/views/card/components/ActivityList.tsx:92
msgid "assigned <0>{memberName}</0> to the card"
msgstr "assigned <0>{memberName}</0> to the card"
#~ msgid "assigned <0>{memberName}</0> to the card"
#~ msgstr "assigned <0>{memberName}</0> to the card"
#: src/views/boards/components/TemplateBoards.tsx:46
msgid "Awaiting Customer"
@@ -198,7 +250,7 @@ msgstr "Billing portal"
msgid "Blog Post"
msgstr "Blog Post"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Board"
msgstr "Board"
@@ -214,7 +266,8 @@ msgstr "Board name cannot exceed 100 characters"
msgid "Board name is required"
msgstr "Board name is required"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:143
#: src/views/board/index.tsx:357
msgid "Board not found"
msgstr "Board not found"
@@ -226,7 +279,7 @@ msgstr "Board URL can only contain letters, numbers, and hyphens"
msgid "Board URL cannot exceed 60 characters"
msgstr "Board URL cannot exceed 60 characters"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "Board URL copied to clipboard"
@@ -273,16 +326,18 @@ msgstr "Bug Report"
#: src/views/settings/components/CustomURLConfirmation.tsx:50
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
#: src/views/card/components/DeleteCardConfirmation.tsx:86
#: src/views/card/components/Comment.tsx:156
#: src/views/card/components/Comment.tsx:158
#: src/components/DeleteLabelConfirmation.tsx:49
msgid "Cancel"
msgstr "Cancel"
#: src/views/card/index.tsx:234
#: src/views/card/index.tsx:259
msgid "Card not found"
msgstr "Card not found"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:307
msgid "Card title"
msgstr "Card title"
@@ -295,6 +350,10 @@ msgstr "Change the language of the app."
msgid "Check your inbox"
msgstr "Check your inbox"
#: src/views/card/components/NewChecklistForm.tsx:118
msgid "Checklist name"
msgstr "Checklist name"
#: src/views/board/components/Filters.tsx:155
msgid "Clear filters"
msgstr "Clear filters"
@@ -332,6 +391,15 @@ msgstr "Complete"
msgid "Complete control and ownership:"
msgstr "Complete control and ownership:"
#: src/views/card/components/ActivityList.tsx:74
msgid "completed a checklist item"
msgstr "completed a checklist item"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:187
msgid "completed checklist item <0>{0}</0>"
msgstr "completed checklist item <0>{0}</0>"
#: src/views/settings/components/CustomURLConfirmation.tsx:42
msgid "Confirm URL change"
msgstr "Confirm URL change"
@@ -375,8 +443,8 @@ msgid "Control who can view and edit your boards."
msgstr "Control who can view and edit your boards."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/components/LabelForm.tsx:212
#: src/views/board/components/NewCardForm.tsx:458
#: src/components/LabelForm.tsx:213
msgid "Create another"
msgstr "Create another"
@@ -392,15 +460,19 @@ msgstr "Create another"
msgid "Create board"
msgstr "Create board"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:468
msgid "Create card"
msgstr "Create card"
#: src/components/LabelForm.tsx:233
#: src/views/card/components/NewChecklistForm.tsx:134
msgid "Create checklist"
msgstr "Create checklist"
#: src/components/LabelForm.tsx:238
msgid "Create label"
msgstr "Create label"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Create list"
@@ -413,11 +485,11 @@ msgid "Create new key"
msgstr "Create new key"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:394
msgid "Create new label"
msgstr "Create new label"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:425
msgid "Create new list"
msgstr "Create new list"
@@ -426,7 +498,7 @@ msgstr "Create new list"
msgid "Create workspace"
msgstr "Create workspace"
#: src/views/card/components/ActivityList.tsx:52
#: src/views/card/components/ActivityList.tsx:61
msgid "created the card"
msgstr "created the card"
@@ -451,8 +523,10 @@ msgid "Dark"
msgstr "Dark"
#: src/views/card/components/DeleteCommentConfirmation.tsx:82
#: src/views/card/components/DeleteChecklistConfirmation.tsx:66
#: src/views/card/components/DeleteCardConfirmation.tsx:92
#: src/components/LabelForm.tsx:226
#: src/components/LabelForm.tsx:228
#: src/components/DeleteLabelConfirmation.tsx:51
msgid "Delete"
msgstr "Delete"
@@ -466,11 +540,11 @@ msgstr "Delete account"
msgid "Delete board"
msgstr "Delete board"
#: src/views/card/components/Dropdown.tsx:14
#: src/views/card/components/Dropdown.tsx:25
msgid "Delete card"
msgstr "Delete card"
#: src/views/card/components/Comment.tsx:89
#: src/views/card/components/Comment.tsx:91
msgid "Delete comment"
msgstr "Delete comment"
@@ -484,6 +558,24 @@ msgstr "Delete list"
msgid "Delete workspace"
msgstr "Delete workspace"
#: src/views/card/components/ActivityList.tsx:71
msgid "deleted a checklist"
msgstr "deleted a checklist"
#: src/views/card/components/ActivityList.tsx:76
msgid "deleted a checklist item"
msgstr "deleted a checklist item"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:162
msgid "deleted checklist <0>{0}</0>"
msgstr "deleted checklist <0>{0}</0>"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:205
msgid "deleted checklist item <0>{0}</0>"
msgstr "deleted checklist item <0>{0}</0>"
#: src/views/boards/components/TemplateBoards.tsx:32
msgid "Design"
msgstr "Design"
@@ -546,11 +638,11 @@ msgstr "Done"
msgid "Edit board URL"
msgstr "Edit board URL"
#: src/views/card/components/Comment.tsx:80
#: src/views/card/components/Comment.tsx:82
msgid "Edit comment"
msgstr "Edit comment"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "Edit label"
msgstr "Edit label"
@@ -566,7 +658,7 @@ msgstr "Editing"
msgid "email"
msgstr "email"
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/InviteMemberForm.tsx:99
msgid "Email"
msgstr "Email"
@@ -590,6 +682,10 @@ msgstr "Enter your password"
msgid "Error deleting account"
msgstr "Error deleting account"
#: src/components/DeleteLabelConfirmation.tsx:25
msgid "Error deleting label"
msgstr "Error deleting label"
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:45
msgid "Error deleting workspace"
msgstr "Error deleting workspace"
@@ -598,6 +694,11 @@ msgstr "Error deleting workspace"
msgid "Error disconnecting Trello"
msgstr "Error disconnecting Trello"
#: src/views/members/components/InviteMemberForm.tsx:57
#: src/views/members/components/InviteMemberForm.tsx:63
msgid "Error inviting member"
msgstr "Error inviting member"
#: src/views/settings/components/UpdateDisplayNameForm.tsx:54
msgid "Error updating display name"
msgstr "Error updating display name"
@@ -713,7 +814,7 @@ msgstr "Get Started"
msgid "Get started by creating a new board"
msgstr "Get started by creating a new board"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:417
msgid "Get started by creating a new list"
msgstr "Get started by creating a new list"
@@ -840,11 +941,19 @@ msgstr "Integrations"
msgid "Interviewing"
msgstr "Interviewing"
#: src/views/members/components/InviteMemberForm.tsx:26
msgid "Invalid email address"
msgstr "Invalid email address"
#: src/views/members/index.tsx:169
msgid "Invite"
msgstr "Invite"
#: src/views/members/components/InviteMemberForm.tsx:82
#: src/views/members/components/InviteMemberForm.tsx:113
msgid "Invite another"
msgstr "Invite another"
#: src/views/members/components/InviteMemberForm.tsx:123
#: src/views/card/components/MemberSelector.tsx:112
msgid "Invite member"
msgstr "Invite member"
@@ -866,8 +975,8 @@ msgstr "Kanban reimagined"
msgid "Keep in mind that this action is irreversible."
msgstr "Keep in mind that this action is irreversible."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/card/index.tsx:118
#: src/views/board/components/NewCardForm.tsx:398
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Labels"
@@ -896,11 +1005,11 @@ msgstr "License"
msgid "Light"
msgstr "Light"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Link copied"
#: src/views/card/index.tsx:105
#: src/views/card/index.tsx:110
msgid "List"
msgstr "List"
@@ -928,13 +1037,22 @@ msgstr "Low Priority"
msgid "magic link"
msgstr "magic link"
#: src/views/card/components/ActivityList.tsx:75
msgid "marked a checklist item as incomplete"
msgstr "marked a checklist item as incomplete"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:196
msgid "marked checklist item <0>{0}</0> as incomplete"
msgstr "marked checklist item <0>{0}</0> as incomplete"
#: src/views/boards/components/TemplateBoards.tsx:17
msgid "Medium Priority"
msgstr "Medium Priority"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/card/index.tsx:126
#: src/views/board/components/NewCardForm.tsx:357
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -949,16 +1067,22 @@ msgstr "Members | {0}"
msgid "Monthly"
msgstr "Monthly"
#: src/views/card/components/ActivityList.tsx:81
msgid "moved the card from <0>{fromList}</0> to<1>{toList}</1>"
msgstr "moved the card from <0>{fromList}</0> to<1>{toList}</1>"
#. placeholder {0}: truncate(fromList)
#. placeholder {1}: truncate(toList)
#: src/views/card/components/ActivityList.tsx:98
msgid "moved the card from <0>{0}</0> to<1>{1}</1>"
msgstr "moved the card from <0>{0}</0> to<1>{1}</1>"
#: src/views/card/components/ActivityList.tsx:55
#: src/views/card/components/ActivityList.tsx:81
#~ msgid "moved the card from <0>{fromList}</0> to<1>{toList}</1>"
#~ msgstr "moved the card from <0>{fromList}</0> to<1>{toList}</1>"
#: src/views/card/components/ActivityList.tsx:64
msgid "moved the card to another list"
msgstr "moved the card to another list"
#: src/views/boards/components/NewBoardForm.tsx:99
#: src/components/LabelForm.tsx:134
#: src/components/LabelForm.tsx:135
msgid "Name"
msgstr "Name"
@@ -974,19 +1098,23 @@ msgstr "New"
msgid "New board"
msgstr "New board"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:290
msgid "New card"
msgstr "New card"
#: src/views/card/components/NewChecklistForm.tsx:102
msgid "New checklist"
msgstr "New checklist"
#: src/views/boards/components/ImportBoardsForm.tsx:322
msgid "New import"
msgstr "New import"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "New label"
msgstr "New label"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:394
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "New list"
@@ -1023,7 +1151,7 @@ msgstr "No boards found"
msgid "No credit card required"
msgstr "No credit card required"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:414
msgid "No lists"
msgstr "No lists"
@@ -1104,24 +1232,32 @@ msgstr "Please select a file to upload."
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
#: src/views/settings/components/Avatar.tsx:40
#: src/views/settings/components/Avatar.tsx:98
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
#: src/views/card/index.tsx:159
#: src/views/card/index.tsx:173
#: src/views/card/components/NewCommentForm.tsx:31
#: src/views/card/components/NewChecklistItemForm.tsx:89
#: src/views/card/components/NewChecklistForm.tsx:70
#: src/views/card/components/MemberSelector.tsx:80
#: src/views/card/components/ListSelector.tsx:53
#: src/views/card/components/LabelSelector.tsx:73
#: src/views/card/components/DeleteCommentConfirmation.tsx:45
#: src/views/card/components/DeleteChecklistConfirmation.tsx:37
#: src/views/card/components/DeleteCardConfirmation.tsx:52
#: src/views/card/components/Comment.tsx:62
#: src/views/card/components/Comment.tsx:64
#: src/views/card/components/ChecklistNameInput.tsx:46
#: src/views/card/components/ChecklistItemRow.tsx:60
#: src/views/card/components/ChecklistItemRow.tsx:88
#: src/views/boards/components/ImportBoardsForm.tsx:205
#: src/views/board/index.tsx:135
#: src/views/board/index.tsx:191
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:190
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:174
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
#: src/components/DeleteLabelConfirmation.tsx:26
msgid "Please try again later, or contact customer support."
msgstr "Please try again later, or contact customer support."
@@ -1187,17 +1323,36 @@ msgstr "Remove"
msgid "Remove member"
msgstr "Remove member"
#: src/views/card/components/ActivityList.tsx:57
#: src/views/card/components/ActivityList.tsx:66
msgid "removed a label from the card"
msgstr "removed a label from the card"
#: src/views/card/components/ActivityList.tsx:59
#: src/views/card/components/ActivityList.tsx:68
msgid "removed a member from the card"
msgstr "removed a member from the card"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:138
msgid "removed label <0>{0}</0>"
msgstr "removed label <0>{0}</0>"
#: src/views/card/components/ActivityList.tsx:118
msgid "removed label <0>{label}</0>"
msgstr "removed label <0>{label}</0>"
#~ msgid "removed label <0>{label}</0>"
#~ msgstr "removed label <0>{label}</0>"
#: src/views/card/components/ActivityList.tsx:70
msgid "renamed a checklist"
msgstr "renamed a checklist"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:154
msgid "renamed checklist <0>{0}</0>"
msgstr "renamed checklist <0>{0}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:178
msgid "renamed checklist item to <0>{0}</0>"
msgstr "renamed checklist item to <0>{0}</0>"
#: src/views/boards/components/TemplateBoards.tsx:76
msgid "Research"
@@ -1232,7 +1387,7 @@ msgstr "Role"
msgid "Run on your own infrastructure"
msgstr "Run on your own infrastructure"
#: src/views/card/components/Comment.tsx:163
#: src/views/card/components/Comment.tsx:165
msgid "Save"
msgstr "Save"
@@ -1260,7 +1415,7 @@ msgstr "Self Host"
msgid "Self host with Github"
msgstr "Self host with Github"
#: src/views/card/components/ActivityList.tsx:89
#: src/views/card/components/ActivityList.tsx:107
msgid "self-assigned the card"
msgstr "self-assigned the card"
@@ -1383,10 +1538,16 @@ msgid "They won't be able to access this workspace."
msgstr "They won't be able to access this workspace."
#: src/views/card/components/DeleteCommentConfirmation.tsx:68
#: src/views/card/components/DeleteChecklistConfirmation.tsx:58
#: src/views/card/components/DeleteCardConfirmation.tsx:78
#: src/components/DeleteLabelConfirmation.tsx:44
msgid "This action can't be undone."
msgstr "This action can't be undone."
#: src/views/public/board/index.tsx:146
msgid "This board is private or does not exist"
msgstr "This board is private or does not exist"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "This board URL has already been taken"
@@ -1432,14 +1593,22 @@ msgstr "Trello imports"
msgid "Triaging"
msgstr "Triaging"
#: src/views/card/components/NewChecklistItemForm.tsx:88
msgid "Unable to add checklist item"
msgstr "Unable to add checklist item"
#: src/views/card/components/NewCommentForm.tsx:30
msgid "Unable to add comment"
msgstr "Unable to add comment"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:171
msgid "Unable to create card"
msgstr "Unable to create card"
#: src/views/card/components/NewChecklistForm.tsx:69
msgid "Unable to create checklist"
msgstr "Unable to create checklist"
#: src/views/board/components/NewListForm.tsx:77
msgid "Unable to create list"
msgstr "Unable to create list"
@@ -1452,6 +1621,14 @@ msgstr "Unable to create workspace"
msgid "Unable to delete card"
msgstr "Unable to delete card"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:36
msgid "Unable to delete checklist"
msgstr "Unable to delete checklist"
#: src/views/card/components/ChecklistItemRow.tsx:87
msgid "Unable to delete checklist item"
msgstr "Unable to delete checklist item"
#: src/views/card/components/DeleteCommentConfirmation.tsx:44
msgid "Unable to delete comment"
msgstr "Unable to delete comment"
@@ -1472,12 +1649,20 @@ msgstr "Unable to update board URL"
msgid "Unable to update board visibility"
msgstr "Unable to update board visibility"
#: src/views/card/index.tsx:158
#: src/views/board/index.tsx:190
#: src/views/card/index.tsx:172
#: src/views/board/index.tsx:189
msgid "Unable to update card"
msgstr "Unable to update card"
#: src/views/card/components/Comment.tsx:61
#: src/views/card/components/ChecklistNameInput.tsx:45
msgid "Unable to update checklist"
msgstr "Unable to update checklist"
#: src/views/card/components/ChecklistItemRow.tsx:59
msgid "Unable to update checklist item"
msgstr "Unable to update checklist item"
#: src/views/card/components/Comment.tsx:63
msgid "Unable to update comment"
msgstr "Unable to update comment"
@@ -1486,7 +1671,7 @@ msgid "Unable to update labels"
msgstr "Unable to update labels"
#: src/views/card/components/ListSelector.tsx:52
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:133
msgid "Unable to update list"
msgstr "Unable to update list"
@@ -1494,11 +1679,16 @@ msgstr "Unable to update list"
msgid "Unable to update members"
msgstr "Unable to update members"
#: src/views/card/components/ActivityList.tsx:102
msgid "unassigned <0>{memberName}</0> from the card"
msgstr "unassigned <0>{memberName}</0> from the card"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:121
msgid "unassigned <0>{0}</0> from the card"
msgstr "unassigned <0>{0}</0> from the card"
#: src/views/card/components/ActivityList.tsx:99
#: src/views/card/components/ActivityList.tsx:102
#~ msgid "unassigned <0>{memberName}</0> from the card"
#~ msgstr "unassigned <0>{memberName}</0> from the card"
#: src/views/card/components/ActivityList.tsx:118
msgid "unassigned themselves from the card"
msgstr "unassigned themselves from the card"
@@ -1530,25 +1720,34 @@ msgstr "Unlimited lists"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Update"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:238
msgid "Update label"
msgstr "Update label"
#: src/views/card/components/ActivityList.tsx:54
#: src/views/card/components/ActivityList.tsx:73
msgid "updated a checklist item"
msgstr "updated a checklist item"
#: src/views/card/components/ActivityList.tsx:63
msgid "updated the description"
msgstr "updated the description"
#: src/views/card/components/ActivityList.tsx:53
#: src/views/card/components/ActivityList.tsx:62
msgid "updated the title"
msgstr "updated the title"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:90
msgid "updated the title to <0>{0}</0>"
msgstr "updated the title to <0>{0}</0>"
#: src/views/card/components/ActivityList.tsx:73
msgid "updated the title to <0>{toTitle}</0>"
msgstr "updated the title to <0>{toTitle}</0>"
#~ msgid "updated the title to <0>{toTitle}</0>"
#~ msgstr "updated the title to <0>{toTitle}</0>"
#: src/views/settings/components/CustomURLConfirmation.tsx:52
msgid "Upgrade"
@@ -1579,6 +1778,10 @@ msgstr "Use template"
msgid "User"
msgstr "User"
#: src/views/members/components/InviteMemberForm.tsx:58
msgid "User is already a member of this workspace"
msgstr "User is already a member of this workspace"
#: src/views/boards/components/TemplateBoards.tsx:37
msgid "Video"
msgstr "Video"
@@ -1595,10 +1798,18 @@ msgstr "View and manage your billing and subscription."
msgid "View docs"
msgstr "View docs"
#: src/views/public/board/index.tsx:120
msgid "View only"
msgstr "View only"
#: src/views/home/components/Features.tsx:147
msgid "View our roadmap."
msgstr "View our roadmap."
#: src/views/public/board/index.tsx:149
msgid "View workspace"
msgstr "View workspace"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Visibility"
@@ -1635,7 +1846,7 @@ msgstr "When Trello launched in 2011, it blew everyone away with its carefully d
msgid "Why make an open source Trello?"
msgstr "Why make an open source Trello?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Workspace"
msgstr "Workspace"

File diff suppressed because one or more lines are too long

View File

@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: src/views/card/components/Comment.tsx:121
#: src/views/card/components/Comment.tsx:123
msgid " (edited)"
msgstr " (editado)"
@@ -23,12 +23,12 @@ msgstr ", o consulta nuestra"
#. placeholder {0}: card?.title ?? "Card"
#. placeholder {1}: board?.name ?? "Board"
#: src/views/card/index.tsx:189
#: src/views/card/index.tsx:214
msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:432
msgid "{0} labels"
msgstr "{0} etiquetas"
@@ -48,8 +48,8 @@ msgstr "Una aplicación kanban potente y flexible que te ayuda a organizar el tr
msgid "Account deleted"
msgstr "Cuenta eliminada"
#: src/views/public/board/CardModal.tsx:104
#: src/views/card/index.tsx:256
#: src/views/public/board/CardModal.tsx:140
#: src/views/card/index.tsx:288
msgid "Activity"
msgstr "Actividad"
@@ -62,35 +62,66 @@ msgid "Add a card"
msgstr "Añadir una tarjeta"
#: src/views/card/components/NewCommentForm.tsx:57
#: src/views/card/components/Comment.tsx:144
#: src/views/card/components/Comment.tsx:146
msgid "Add a comment..."
msgstr "Añadir un comentario..."
#: src/components/Editor.tsx:302
msgid "Add description... (type '/' to open commands)"
msgstr "Añadir descripción... (escribe '/' para abrir comandos)"
#: src/views/card/components/NewChecklistItemForm.tsx:140
msgid "Add an item..."
msgstr "Añadir un elemento..."
#: src/views/card/components/Dropdown.tsx:18
msgid "Add checklist"
msgstr "Añadir lista de verificación"
#: src/components/Editor.tsx:442
msgid "Add description... (type '/' to open commands or '@' to mention)"
msgstr "Añadir descripción... (escribe '/' para abrir comandos o '@' para mencionar)"
#: src/views/card/components/ChecklistItemRow.tsx:172
msgid "Add details..."
msgstr "Añadir detalles..."
#: src/views/card/components/LabelSelector.tsx:110
#: src/views/card/components/LabelSelector.tsx:118
msgid "Add label"
msgstr "Añadir etiqueta"
#: src/views/members/components/InviteMemberForm.tsx:50
#: src/views/members/components/InviteMemberForm.tsx:85
#: src/views/card/components/MemberSelector.tsx:130
msgid "Add member"
msgstr "Añadir miembro"
#: src/views/card/components/ActivityList.tsx:56
#: src/views/card/components/ActivityList.tsx:69
msgid "added a checklist"
msgstr "añadió una lista de verificación"
#: src/views/card/components/ActivityList.tsx:72
msgid "added a checklist item"
msgstr "añadió un elemento a la lista de verificación"
#: src/views/card/components/ActivityList.tsx:65
msgid "added a label to the card"
msgstr "añadió una etiqueta a la tarjeta"
#: src/views/card/components/ActivityList.tsx:58
#: src/views/card/components/ActivityList.tsx:67
msgid "added a member to the card"
msgstr "añadió un miembro a la tarjeta"
#: src/views/card/components/ActivityList.tsx:110
msgid "added label <0>{label}</0>"
msgstr "añadió la etiqueta <0>{label}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:146
msgid "added checklist <0>{0}</0>"
msgstr "añadió la lista de verificación <0>{0}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:170
msgid "added checklist item <0>{0}</0>"
msgstr "añadió el elemento <0>{0}</0> a la lista de verificación"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:130
msgid "added label <0>{0}</0>"
msgstr "añadió la etiqueta <0>{0}</0>"
#: src/views/home/components/Pricing.tsx:55
msgid "Admin roles"
@@ -141,17 +172,26 @@ msgstr "¿Estás seguro de que quieres eliminar el espacio de trabajo {0}?"
msgid "Are you sure you want to delete this card?"
msgstr "¿Estás seguro de que quieres eliminar esta tarjeta?"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:55
msgid "Are you sure you want to delete this checklist?"
msgstr "¿Estás seguro de que quieres eliminar esta lista de verificación?"
#: src/views/card/components/DeleteCommentConfirmation.tsx:65
msgid "Are you sure you want to delete this comment?"
msgstr "¿Estás seguro de que quieres eliminar este comentario?"
#: src/components/DeleteLabelConfirmation.tsx:41
msgid "Are you sure you want to delete this label?"
msgstr "¿Estás seguro de que quieres eliminar esta etiqueta?"
#: src/views/settings/components/DeleteAccountConfirmation.tsx:54
msgid "Are you sure you want to delete your account?"
msgstr "¿Estás seguro de que quieres eliminar tu cuenta?"
#: src/views/card/components/ActivityList.tsx:92
msgid "assigned <0>{memberName}</0> to the card"
msgstr "asignó a <0>{memberName}</0> a la tarjeta"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:110
msgid "assigned <0>{0}</0> to the card"
msgstr "asignó <0>{0}</0> a la tarjeta"
#: src/views/boards/components/TemplateBoards.tsx:46
msgid "Awaiting Customer"
@@ -178,7 +218,7 @@ msgstr "Portal de facturación"
msgid "Blog Post"
msgstr "Entrada de blog"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Board"
msgstr "Tablero"
@@ -190,7 +230,8 @@ msgstr "El nombre del tablero no puede exceder los 100 caracteres"
msgid "Board name is required"
msgstr "El nombre del tablero es obligatorio"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:143
#: src/views/board/index.tsx:357
msgid "Board not found"
msgstr "Tablero no encontrado"
@@ -202,7 +243,7 @@ msgstr "La URL del tablero solo puede contener letras, números y guiones"
msgid "Board URL cannot exceed 60 characters"
msgstr "La URL del tablero no puede exceder los 60 caracteres"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "URL del tablero copiada al portapapeles"
@@ -245,16 +286,18 @@ msgstr "Informe de error"
#: src/views/settings/components/CustomURLConfirmation.tsx:50
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
#: src/views/card/components/DeleteCardConfirmation.tsx:86
#: src/views/card/components/Comment.tsx:156
#: src/views/card/components/Comment.tsx:158
#: src/components/DeleteLabelConfirmation.tsx:49
msgid "Cancel"
msgstr "Cancelar"
#: src/views/card/index.tsx:234
#: src/views/card/index.tsx:259
msgid "Card not found"
msgstr "Tarjeta no encontrada"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:307
msgid "Card title"
msgstr "Título de la tarjeta"
@@ -267,6 +310,10 @@ msgstr "Cambiar el idioma de la aplicación."
msgid "Check your inbox"
msgstr "Revisa tu bandeja de entrada"
#: src/views/card/components/NewChecklistForm.tsx:118
msgid "Checklist name"
msgstr "Nombre de la lista de verificación"
#: src/views/board/components/Filters.tsx:155
msgid "Clear filters"
msgstr "Borrar filtros"
@@ -304,6 +351,15 @@ msgstr "Completado"
msgid "Complete control and ownership:"
msgstr "Control y propiedad completos:"
#: src/views/card/components/ActivityList.tsx:74
msgid "completed a checklist item"
msgstr "completó un elemento de la lista de verificación"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:187
msgid "completed checklist item <0>{0}</0>"
msgstr "completó el elemento <0>{0}</0> de la lista de verificación"
#: src/views/settings/components/CustomURLConfirmation.tsx:42
msgid "Confirm URL change"
msgstr "Confirmar cambio de URL"
@@ -347,8 +403,8 @@ msgid "Control who can view and edit your boards."
msgstr "Controla quién puede ver y editar tus tableros."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/components/LabelForm.tsx:212
#: src/views/board/components/NewCardForm.tsx:458
#: src/components/LabelForm.tsx:213
msgid "Create another"
msgstr "Crear otro"
@@ -356,15 +412,19 @@ msgstr "Crear otro"
msgid "Create board"
msgstr "Crear tablero"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:468
msgid "Create card"
msgstr "Crear tarjeta"
#: src/components/LabelForm.tsx:233
#: src/views/card/components/NewChecklistForm.tsx:134
msgid "Create checklist"
msgstr "Crear lista de verificación"
#: src/components/LabelForm.tsx:238
msgid "Create label"
msgstr "Crear etiqueta"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Crear lista"
@@ -377,11 +437,11 @@ msgid "Create new key"
msgstr "Crear nueva clave"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:394
msgid "Create new label"
msgstr "Crear nueva etiqueta"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:425
msgid "Create new list"
msgstr "Crear nueva lista"
@@ -390,7 +450,7 @@ msgstr "Crear nueva lista"
msgid "Create workspace"
msgstr "Crear espacio de trabajo"
#: src/views/card/components/ActivityList.tsx:52
#: src/views/card/components/ActivityList.tsx:61
msgid "created the card"
msgstr "creó la tarjeta"
@@ -415,8 +475,10 @@ msgid "Dark"
msgstr "Oscuro"
#: src/views/card/components/DeleteCommentConfirmation.tsx:82
#: src/views/card/components/DeleteChecklistConfirmation.tsx:66
#: src/views/card/components/DeleteCardConfirmation.tsx:92
#: src/components/LabelForm.tsx:226
#: src/components/LabelForm.tsx:228
#: src/components/DeleteLabelConfirmation.tsx:51
msgid "Delete"
msgstr "Eliminar"
@@ -430,11 +492,11 @@ msgstr "Eliminar cuenta"
msgid "Delete board"
msgstr "Eliminar tablero"
#: src/views/card/components/Dropdown.tsx:14
#: src/views/card/components/Dropdown.tsx:25
msgid "Delete card"
msgstr "Eliminar tarjeta"
#: src/views/card/components/Comment.tsx:89
#: src/views/card/components/Comment.tsx:91
msgid "Delete comment"
msgstr "Eliminar comentario"
@@ -448,6 +510,24 @@ msgstr "Eliminar lista"
msgid "Delete workspace"
msgstr "Eliminar espacio de trabajo"
#: src/views/card/components/ActivityList.tsx:71
msgid "deleted a checklist"
msgstr "eliminó una lista de verificación"
#: src/views/card/components/ActivityList.tsx:76
msgid "deleted a checklist item"
msgstr "eliminó un elemento de la lista de verificación"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:162
msgid "deleted checklist <0>{0}</0>"
msgstr "eliminó la lista de verificación <0>{0}</0>"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:205
msgid "deleted checklist item <0>{0}</0>"
msgstr "eliminó el elemento <0>{0}</0> de la lista de verificación"
#: src/views/boards/components/TemplateBoards.tsx:32
msgid "Design"
msgstr "Diseño"
@@ -510,11 +590,11 @@ msgstr "Terminado"
msgid "Edit board URL"
msgstr "Editar URL del tablero"
#: src/views/card/components/Comment.tsx:80
#: src/views/card/components/Comment.tsx:82
msgid "Edit comment"
msgstr "Editar comentario"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "Edit label"
msgstr "Editar etiqueta"
@@ -530,7 +610,7 @@ msgstr "Editando"
msgid "email"
msgstr "correo electrónico"
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/InviteMemberForm.tsx:99
msgid "Email"
msgstr "Correo electrónico"
@@ -554,6 +634,10 @@ msgstr "Introduce tu contraseña"
msgid "Error deleting account"
msgstr "Error al eliminar la cuenta"
#: src/components/DeleteLabelConfirmation.tsx:25
msgid "Error deleting label"
msgstr "Error al eliminar la etiqueta"
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:45
msgid "Error deleting workspace"
msgstr "Error al eliminar el espacio de trabajo"
@@ -562,6 +646,11 @@ msgstr "Error al eliminar el espacio de trabajo"
msgid "Error disconnecting Trello"
msgstr "Error al desconectar Trello"
#: src/views/members/components/InviteMemberForm.tsx:57
#: src/views/members/components/InviteMemberForm.tsx:63
msgid "Error inviting member"
msgstr "Error al invitar al miembro"
#: src/views/settings/components/UpdateDisplayNameForm.tsx:54
msgid "Error updating display name"
msgstr "Error al actualizar el nombre visible"
@@ -676,7 +765,7 @@ msgstr "Comenzar"
msgid "Get started by creating a new board"
msgstr "Comienza creando un nuevo tablero"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:417
msgid "Get started by creating a new list"
msgstr "Comienza creando una nueva lista"
@@ -799,11 +888,19 @@ msgstr "Integraciones"
msgid "Interviewing"
msgstr "Entrevistando"
#: src/views/members/components/InviteMemberForm.tsx:26
msgid "Invalid email address"
msgstr "Dirección de correo electrónico no válida"
#: src/views/members/index.tsx:169
msgid "Invite"
msgstr "Invitar"
#: src/views/members/components/InviteMemberForm.tsx:82
#: src/views/members/components/InviteMemberForm.tsx:113
msgid "Invite another"
msgstr "Invitar a otro"
#: src/views/members/components/InviteMemberForm.tsx:123
#: src/views/card/components/MemberSelector.tsx:112
msgid "Invite member"
msgstr "Invitar miembro"
@@ -825,8 +922,8 @@ msgstr "Kanban reinventado"
msgid "Keep in mind that this action is irreversible."
msgstr "Ten en cuenta que esta acción es irreversible."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/card/index.tsx:118
#: src/views/board/components/NewCardForm.tsx:398
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Etiquetas"
@@ -855,11 +952,11 @@ msgstr "Licencia"
msgid "Light"
msgstr "Claro"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Enlace copiado"
#: src/views/card/index.tsx:105
#: src/views/card/index.tsx:110
msgid "List"
msgstr "Lista"
@@ -887,13 +984,22 @@ msgstr "Prioridad baja"
msgid "magic link"
msgstr "enlace mágico"
#: src/views/card/components/ActivityList.tsx:75
msgid "marked a checklist item as incomplete"
msgstr "marcó un elemento de la lista de verificación como incompleto"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:196
msgid "marked checklist item <0>{0}</0> as incomplete"
msgstr "marcó el elemento <0>{0}</0> de la lista de verificación como incompleto"
#: src/views/boards/components/TemplateBoards.tsx:17
msgid "Medium Priority"
msgstr "Prioridad media"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/card/index.tsx:126
#: src/views/board/components/NewCardForm.tsx:357
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -908,16 +1014,18 @@ msgstr "Miembros | {0}"
msgid "Monthly"
msgstr "Mensual"
#: src/views/card/components/ActivityList.tsx:81
msgid "moved the card from <0>{fromList}</0> to<1>{toList}</1>"
msgstr "movió la tarjeta de <0>{fromList}</0> a<1>{toList}</1>"
#. placeholder {0}: truncate(fromList)
#. placeholder {1}: truncate(toList)
#: src/views/card/components/ActivityList.tsx:98
msgid "moved the card from <0>{0}</0> to<1>{1}</1>"
msgstr "movió la tarjeta de <0>{0}</0> a<1>{1}</1>"
#: src/views/card/components/ActivityList.tsx:55
#: src/views/card/components/ActivityList.tsx:64
msgid "moved the card to another list"
msgstr "movió la tarjeta a otra lista"
#: src/views/boards/components/NewBoardForm.tsx:99
#: src/components/LabelForm.tsx:134
#: src/components/LabelForm.tsx:135
msgid "Name"
msgstr "Nombre"
@@ -933,19 +1041,23 @@ msgstr "Nuevo"
msgid "New board"
msgstr "Nuevo tablero"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:290
msgid "New card"
msgstr "Nueva tarjeta"
#: src/views/card/components/NewChecklistForm.tsx:102
msgid "New checklist"
msgstr "Nueva lista de verificación"
#: src/views/boards/components/ImportBoardsForm.tsx:322
msgid "New import"
msgstr "Nueva importación"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "New label"
msgstr "Nueva etiqueta"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:394
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "Nueva lista"
@@ -978,7 +1090,7 @@ msgstr "No se encontraron tableros"
msgid "No credit card required"
msgstr "No se requiere tarjeta de crédito"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:414
msgid "No lists"
msgstr "Sin listas"
@@ -1059,24 +1171,32 @@ msgstr "Por favor selecciona un archivo para subir."
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
#: src/views/settings/components/Avatar.tsx:40
#: src/views/settings/components/Avatar.tsx:98
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
#: src/views/card/index.tsx:159
#: src/views/card/index.tsx:173
#: src/views/card/components/NewCommentForm.tsx:31
#: src/views/card/components/NewChecklistItemForm.tsx:89
#: src/views/card/components/NewChecklistForm.tsx:70
#: src/views/card/components/MemberSelector.tsx:80
#: src/views/card/components/ListSelector.tsx:53
#: src/views/card/components/LabelSelector.tsx:73
#: src/views/card/components/DeleteCommentConfirmation.tsx:45
#: src/views/card/components/DeleteChecklistConfirmation.tsx:37
#: src/views/card/components/DeleteCardConfirmation.tsx:52
#: src/views/card/components/Comment.tsx:62
#: src/views/card/components/Comment.tsx:64
#: src/views/card/components/ChecklistNameInput.tsx:46
#: src/views/card/components/ChecklistItemRow.tsx:60
#: src/views/card/components/ChecklistItemRow.tsx:88
#: src/views/boards/components/ImportBoardsForm.tsx:205
#: src/views/board/index.tsx:135
#: src/views/board/index.tsx:191
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:190
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:174
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
#: src/components/DeleteLabelConfirmation.tsx:26
msgid "Please try again later, or contact customer support."
msgstr "Por favor, inténtalo de nuevo más tarde o contacta con atención al cliente."
@@ -1142,17 +1262,32 @@ msgstr "Eliminar"
msgid "Remove member"
msgstr "Eliminar miembro"
#: src/views/card/components/ActivityList.tsx:57
#: src/views/card/components/ActivityList.tsx:66
msgid "removed a label from the card"
msgstr "eliminó una etiqueta de la tarjeta"
#: src/views/card/components/ActivityList.tsx:59
#: src/views/card/components/ActivityList.tsx:68
msgid "removed a member from the card"
msgstr "eliminó un miembro de la tarjeta"
#: src/views/card/components/ActivityList.tsx:118
msgid "removed label <0>{label}</0>"
msgstr "eliminó la etiqueta <0>{label}</0>"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:138
msgid "removed label <0>{0}</0>"
msgstr "eliminó la etiqueta <0>{0}</0>"
#: src/views/card/components/ActivityList.tsx:70
msgid "renamed a checklist"
msgstr "renombró una lista de verificación"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:154
msgid "renamed checklist <0>{0}</0>"
msgstr "renombró la lista de verificación <0>{0}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:178
msgid "renamed checklist item to <0>{0}</0>"
msgstr "renombró el elemento de la lista de verificación a <0>{0}</0>"
#: src/views/boards/components/TemplateBoards.tsx:76
msgid "Research"
@@ -1187,7 +1322,7 @@ msgstr "Rol"
msgid "Run on your own infrastructure"
msgstr "Ejecuta en tu propia infraestructura"
#: src/views/card/components/Comment.tsx:163
#: src/views/card/components/Comment.tsx:165
msgid "Save"
msgstr "Guardar"
@@ -1215,7 +1350,7 @@ msgstr "Autoalojamiento"
msgid "Self host with Github"
msgstr "Autoalojamiento con Github"
#: src/views/card/components/ActivityList.tsx:89
#: src/views/card/components/ActivityList.tsx:107
msgid "self-assigned the card"
msgstr "se autoasignó la tarjeta"
@@ -1334,10 +1469,16 @@ msgid "They won't be able to access this workspace."
msgstr "No podrán acceder a este espacio de trabajo."
#: src/views/card/components/DeleteCommentConfirmation.tsx:68
#: src/views/card/components/DeleteChecklistConfirmation.tsx:58
#: src/views/card/components/DeleteCardConfirmation.tsx:78
#: src/components/DeleteLabelConfirmation.tsx:44
msgid "This action can't be undone."
msgstr "Esta acción no se puede deshacer."
#: src/views/public/board/index.tsx:146
msgid "This board is private or does not exist"
msgstr "Este tablero es privado o no existe"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "Esta URL de tablero ya ha sido utilizada"
@@ -1379,14 +1520,22 @@ msgstr "Importaciones de Trello"
msgid "Triaging"
msgstr "Clasificación"
#: src/views/card/components/NewChecklistItemForm.tsx:88
msgid "Unable to add checklist item"
msgstr "No se puede añadir el elemento a la lista de verificación"
#: src/views/card/components/NewCommentForm.tsx:30
msgid "Unable to add comment"
msgstr "No se puede añadir el comentario"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:171
msgid "Unable to create card"
msgstr "No se puede crear la tarjeta"
#: src/views/card/components/NewChecklistForm.tsx:69
msgid "Unable to create checklist"
msgstr "No se puede crear la lista de verificación"
#: src/views/board/components/NewListForm.tsx:77
msgid "Unable to create list"
msgstr "No se puede crear la lista"
@@ -1399,6 +1548,14 @@ msgstr "No se puede crear el espacio de trabajo"
msgid "Unable to delete card"
msgstr "No se puede eliminar la tarjeta"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:36
msgid "Unable to delete checklist"
msgstr "No se puede eliminar la lista de verificación"
#: src/views/card/components/ChecklistItemRow.tsx:87
msgid "Unable to delete checklist item"
msgstr "No se puede eliminar el elemento de la lista de verificación"
#: src/views/card/components/DeleteCommentConfirmation.tsx:44
msgid "Unable to delete comment"
msgstr "No se puede eliminar el comentario"
@@ -1419,12 +1576,20 @@ msgstr "No se puede actualizar la URL del tablero"
msgid "Unable to update board visibility"
msgstr "No se puede actualizar la visibilidad del tablero"
#: src/views/card/index.tsx:158
#: src/views/board/index.tsx:190
#: src/views/card/index.tsx:172
#: src/views/board/index.tsx:189
msgid "Unable to update card"
msgstr "No se puede actualizar la tarjeta"
#: src/views/card/components/Comment.tsx:61
#: src/views/card/components/ChecklistNameInput.tsx:45
msgid "Unable to update checklist"
msgstr "No se puede actualizar la lista de verificación"
#: src/views/card/components/ChecklistItemRow.tsx:59
msgid "Unable to update checklist item"
msgstr "No se puede actualizar el elemento de la lista de verificación"
#: src/views/card/components/Comment.tsx:63
msgid "Unable to update comment"
msgstr "No se puede actualizar el comentario"
@@ -1433,7 +1598,7 @@ msgid "Unable to update labels"
msgstr "No se pueden actualizar las etiquetas"
#: src/views/card/components/ListSelector.tsx:52
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:133
msgid "Unable to update list"
msgstr "No se puede actualizar la lista"
@@ -1441,11 +1606,12 @@ msgstr "No se puede actualizar la lista"
msgid "Unable to update members"
msgstr "No se pueden actualizar los miembros"
#: src/views/card/components/ActivityList.tsx:102
msgid "unassigned <0>{memberName}</0> from the card"
msgstr "desasignó a <0>{memberName}</0> de la tarjeta"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:121
msgid "unassigned <0>{0}</0> from the card"
msgstr "desasignó <0>{0}</0> de la tarjeta"
#: src/views/card/components/ActivityList.tsx:99
#: src/views/card/components/ActivityList.tsx:118
msgid "unassigned themselves from the card"
msgstr "se desasignó de la tarjeta"
@@ -1477,25 +1643,30 @@ msgstr "Listas ilimitadas"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Actualizar"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:238
msgid "Update label"
msgstr "Actualizar etiqueta"
#: src/views/card/components/ActivityList.tsx:54
#: src/views/card/components/ActivityList.tsx:73
msgid "updated a checklist item"
msgstr "actualizó un elemento de la lista de verificación"
#: src/views/card/components/ActivityList.tsx:63
msgid "updated the description"
msgstr "actualizó la descripción"
#: src/views/card/components/ActivityList.tsx:53
#: src/views/card/components/ActivityList.tsx:62
msgid "updated the title"
msgstr "actualizó el título"
#: src/views/card/components/ActivityList.tsx:73
msgid "updated the title to <0>{toTitle}</0>"
msgstr "actualizó el título a <0>{toTitle}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:90
msgid "updated the title to <0>{0}</0>"
msgstr "actualizó el título a <0>{0}</0>"
#: src/views/settings/components/CustomURLConfirmation.tsx:52
msgid "Upgrade"
@@ -1526,6 +1697,10 @@ msgstr "Usar plantilla"
msgid "User"
msgstr "Usuario"
#: src/views/members/components/InviteMemberForm.tsx:58
msgid "User is already a member of this workspace"
msgstr "El usuario ya es miembro de este espacio de trabajo"
#: src/views/boards/components/TemplateBoards.tsx:37
msgid "Video"
msgstr "Video"
@@ -1542,10 +1717,18 @@ msgstr "Ver y gestionar tu facturación y suscripción."
msgid "View docs"
msgstr "Ver documentación"
#: src/views/public/board/index.tsx:120
msgid "View only"
msgstr "Solo visualización"
#: src/views/home/components/Features.tsx:147
msgid "View our roadmap."
msgstr "Ver nuestra hoja de ruta."
#: src/views/public/board/index.tsx:149
msgid "View workspace"
msgstr "Ver espacio de trabajo"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Visibilidad"
@@ -1578,7 +1761,7 @@ msgstr "Cuando Trello se lanzó en 2011, impresionó a todos con su simplicidad
msgid "Why make an open source Trello?"
msgstr "¿Por qué crear un Trello de código abierto?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Workspace"
msgstr "Espacio de trabajo"

File diff suppressed because one or more lines are too long

View File

@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: src/views/card/components/Comment.tsx:121
#: src/views/card/components/Comment.tsx:123
msgid " (edited)"
msgstr " (modifié)"
@@ -23,12 +23,12 @@ msgstr ", ou consultez notre"
#. placeholder {0}: card?.title ?? "Card"
#. placeholder {1}: board?.name ?? "Board"
#: src/views/card/index.tsx:189
#: src/views/card/index.tsx:214
msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:432
msgid "{0} labels"
msgstr "{0} étiquettes"
@@ -48,8 +48,8 @@ msgstr "Une application kanban puissante et flexible qui vous aide à organiser
msgid "Account deleted"
msgstr "Compte supprimé"
#: src/views/public/board/CardModal.tsx:104
#: src/views/card/index.tsx:256
#: src/views/public/board/CardModal.tsx:140
#: src/views/card/index.tsx:288
msgid "Activity"
msgstr "Activité"
@@ -62,35 +62,66 @@ msgid "Add a card"
msgstr "Ajouter une carte"
#: src/views/card/components/NewCommentForm.tsx:57
#: src/views/card/components/Comment.tsx:144
#: src/views/card/components/Comment.tsx:146
msgid "Add a comment..."
msgstr "Ajouter un commentaire..."
#: src/components/Editor.tsx:302
msgid "Add description... (type '/' to open commands)"
msgstr "Ajouter une description... (tapez '/' pour ouvrir les commandes)"
#: src/views/card/components/NewChecklistItemForm.tsx:140
msgid "Add an item..."
msgstr "Ajouter un élément..."
#: src/views/card/components/Dropdown.tsx:18
msgid "Add checklist"
msgstr "Ajouter une checklist"
#: src/components/Editor.tsx:442
msgid "Add description... (type '/' to open commands or '@' to mention)"
msgstr "Ajouter une description... (tapez '/' pour ouvrir les commandes ou '@' pour mentionner)"
#: src/views/card/components/ChecklistItemRow.tsx:172
msgid "Add details..."
msgstr "Ajouter des détails..."
#: src/views/card/components/LabelSelector.tsx:110
#: src/views/card/components/LabelSelector.tsx:118
msgid "Add label"
msgstr "Ajouter une étiquette"
#: src/views/members/components/InviteMemberForm.tsx:50
#: src/views/members/components/InviteMemberForm.tsx:85
#: src/views/card/components/MemberSelector.tsx:130
msgid "Add member"
msgstr "Ajouter un membre"
#: src/views/card/components/ActivityList.tsx:56
#: src/views/card/components/ActivityList.tsx:69
msgid "added a checklist"
msgstr "a ajouté une checklist"
#: src/views/card/components/ActivityList.tsx:72
msgid "added a checklist item"
msgstr "a ajouté un élément à la checklist"
#: src/views/card/components/ActivityList.tsx:65
msgid "added a label to the card"
msgstr "a ajouté une étiquette à la carte"
#: src/views/card/components/ActivityList.tsx:58
#: src/views/card/components/ActivityList.tsx:67
msgid "added a member to the card"
msgstr "a ajouté un membre à la carte"
#: src/views/card/components/ActivityList.tsx:110
msgid "added label <0>{label}</0>"
msgstr "a ajouté l'étiquette <0>{label}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:146
msgid "added checklist <0>{0}</0>"
msgstr "a ajouté la checklist <0>{0}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:170
msgid "added checklist item <0>{0}</0>"
msgstr "a ajouté l'élément <0>{0}</0> à la checklist"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:130
msgid "added label <0>{0}</0>"
msgstr "a ajouté l'étiquette <0>{0}</0>"
#: src/views/home/components/Pricing.tsx:55
msgid "Admin roles"
@@ -141,17 +172,26 @@ msgstr "Êtes-vous sûr de vouloir supprimer l'espace de travail {0} ?"
msgid "Are you sure you want to delete this card?"
msgstr "Êtes-vous sûr de vouloir supprimer cette carte ?"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:55
msgid "Are you sure you want to delete this checklist?"
msgstr "Êtes-vous sûr de vouloir supprimer cette checklist ?"
#: src/views/card/components/DeleteCommentConfirmation.tsx:65
msgid "Are you sure you want to delete this comment?"
msgstr "Êtes-vous sûr de vouloir supprimer ce commentaire ?"
#: src/components/DeleteLabelConfirmation.tsx:41
msgid "Are you sure you want to delete this label?"
msgstr "Êtes-vous sûr de vouloir supprimer cette étiquette ?"
#: src/views/settings/components/DeleteAccountConfirmation.tsx:54
msgid "Are you sure you want to delete your account?"
msgstr "Êtes-vous sûr de vouloir supprimer votre compte ?"
#: src/views/card/components/ActivityList.tsx:92
msgid "assigned <0>{memberName}</0> to the card"
msgstr "a assigné <0>{memberName}</0> à la carte"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:110
msgid "assigned <0>{0}</0> to the card"
msgstr "a assigné <0>{0}</0> à la carte"
#: src/views/boards/components/TemplateBoards.tsx:46
msgid "Awaiting Customer"
@@ -178,7 +218,7 @@ msgstr "Portail de facturation"
msgid "Blog Post"
msgstr "Article de blog"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Board"
msgstr "Tableau"
@@ -190,7 +230,8 @@ msgstr "Le nom du tableau ne peut pas dépasser 100 caractères"
msgid "Board name is required"
msgstr "Le nom du tableau est requis"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:143
#: src/views/board/index.tsx:357
msgid "Board not found"
msgstr "Tableau introuvable"
@@ -202,7 +243,7 @@ msgstr "L'URL du tableau ne peut contenir que des lettres, des chiffres et des t
msgid "Board URL cannot exceed 60 characters"
msgstr "L'URL du tableau ne peut pas dépasser 60 caractères"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "URL du tableau copiée dans le presse-papiers"
@@ -245,16 +286,18 @@ msgstr "Rapport de bug"
#: src/views/settings/components/CustomURLConfirmation.tsx:50
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
#: src/views/card/components/DeleteCardConfirmation.tsx:86
#: src/views/card/components/Comment.tsx:156
#: src/views/card/components/Comment.tsx:158
#: src/components/DeleteLabelConfirmation.tsx:49
msgid "Cancel"
msgstr "Annuler"
#: src/views/card/index.tsx:234
#: src/views/card/index.tsx:259
msgid "Card not found"
msgstr "Carte introuvable"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:307
msgid "Card title"
msgstr "Titre de la carte"
@@ -267,6 +310,10 @@ msgstr "Changer la langue de l'application."
msgid "Check your inbox"
msgstr "Vérifiez votre boîte de réception"
#: src/views/card/components/NewChecklistForm.tsx:118
msgid "Checklist name"
msgstr "Nom de la checklist"
#: src/views/board/components/Filters.tsx:155
msgid "Clear filters"
msgstr "Effacer les filtres"
@@ -304,6 +351,15 @@ msgstr "Terminé"
msgid "Complete control and ownership:"
msgstr "Contrôle et propriété complets :"
#: src/views/card/components/ActivityList.tsx:74
msgid "completed a checklist item"
msgstr "a terminé un élément de la checklist"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:187
msgid "completed checklist item <0>{0}</0>"
msgstr "a terminé l'élément <0>{0}</0> de la checklist"
#: src/views/settings/components/CustomURLConfirmation.tsx:42
msgid "Confirm URL change"
msgstr "Confirmer le changement d'URL"
@@ -347,8 +403,8 @@ msgid "Control who can view and edit your boards."
msgstr "Contrôlez qui peut voir et modifier vos tableaux."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/components/LabelForm.tsx:212
#: src/views/board/components/NewCardForm.tsx:458
#: src/components/LabelForm.tsx:213
msgid "Create another"
msgstr "Créer un autre"
@@ -356,15 +412,19 @@ msgstr "Créer un autre"
msgid "Create board"
msgstr "Créer un tableau"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:468
msgid "Create card"
msgstr "Créer une carte"
#: src/components/LabelForm.tsx:233
#: src/views/card/components/NewChecklistForm.tsx:134
msgid "Create checklist"
msgstr "Créer une checklist"
#: src/components/LabelForm.tsx:238
msgid "Create label"
msgstr "Créer une étiquette"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Créer une liste"
@@ -377,11 +437,11 @@ msgid "Create new key"
msgstr "Créer une nouvelle clé"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:394
msgid "Create new label"
msgstr "Créer une nouvelle étiquette"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:425
msgid "Create new list"
msgstr "Créer une nouvelle liste"
@@ -390,7 +450,7 @@ msgstr "Créer une nouvelle liste"
msgid "Create workspace"
msgstr "Créer un espace de travail"
#: src/views/card/components/ActivityList.tsx:52
#: src/views/card/components/ActivityList.tsx:61
msgid "created the card"
msgstr "a créé la carte"
@@ -415,8 +475,10 @@ msgid "Dark"
msgstr "Sombre"
#: src/views/card/components/DeleteCommentConfirmation.tsx:82
#: src/views/card/components/DeleteChecklistConfirmation.tsx:66
#: src/views/card/components/DeleteCardConfirmation.tsx:92
#: src/components/LabelForm.tsx:226
#: src/components/LabelForm.tsx:228
#: src/components/DeleteLabelConfirmation.tsx:51
msgid "Delete"
msgstr "Supprimer"
@@ -430,11 +492,11 @@ msgstr "Supprimer le compte"
msgid "Delete board"
msgstr "Supprimer le tableau"
#: src/views/card/components/Dropdown.tsx:14
#: src/views/card/components/Dropdown.tsx:25
msgid "Delete card"
msgstr "Supprimer la carte"
#: src/views/card/components/Comment.tsx:89
#: src/views/card/components/Comment.tsx:91
msgid "Delete comment"
msgstr "Supprimer le commentaire"
@@ -448,6 +510,24 @@ msgstr "Supprimer la liste"
msgid "Delete workspace"
msgstr "Supprimer l'espace de travail"
#: src/views/card/components/ActivityList.tsx:71
msgid "deleted a checklist"
msgstr "a supprimé une checklist"
#: src/views/card/components/ActivityList.tsx:76
msgid "deleted a checklist item"
msgstr "a supprimé un élément de la checklist"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:162
msgid "deleted checklist <0>{0}</0>"
msgstr "a supprimé la checklist <0>{0}</0>"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:205
msgid "deleted checklist item <0>{0}</0>"
msgstr "a supprimé l'élément <0>{0}</0> de la checklist"
#: src/views/boards/components/TemplateBoards.tsx:32
msgid "Design"
msgstr "Design"
@@ -510,11 +590,11 @@ msgstr "Fait"
msgid "Edit board URL"
msgstr "Modifier l'URL du tableau"
#: src/views/card/components/Comment.tsx:80
#: src/views/card/components/Comment.tsx:82
msgid "Edit comment"
msgstr "Modifier le commentaire"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "Edit label"
msgstr "Modifier l'étiquette"
@@ -530,7 +610,7 @@ msgstr "Édition"
msgid "email"
msgstr "e-mail"
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/InviteMemberForm.tsx:99
msgid "Email"
msgstr "E-mail"
@@ -554,6 +634,10 @@ msgstr "Saisissez votre mot de passe"
msgid "Error deleting account"
msgstr "Erreur lors de la suppression du compte"
#: src/components/DeleteLabelConfirmation.tsx:25
msgid "Error deleting label"
msgstr "Erreur lors de la suppression de l'étiquette"
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:45
msgid "Error deleting workspace"
msgstr "Erreur lors de la suppression de l'espace de travail"
@@ -562,6 +646,11 @@ msgstr "Erreur lors de la suppression de l'espace de travail"
msgid "Error disconnecting Trello"
msgstr "Erreur lors de la déconnexion de Trello"
#: src/views/members/components/InviteMemberForm.tsx:57
#: src/views/members/components/InviteMemberForm.tsx:63
msgid "Error inviting member"
msgstr "Erreur lors de l'invitation du membre"
#: src/views/settings/components/UpdateDisplayNameForm.tsx:54
msgid "Error updating display name"
msgstr "Erreur lors de la mise à jour du nom d'affichage"
@@ -676,7 +765,7 @@ msgstr "Commencer"
msgid "Get started by creating a new board"
msgstr "Commencez par créer un nouveau tableau"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:417
msgid "Get started by creating a new list"
msgstr "Commencez par créer une nouvelle liste"
@@ -799,11 +888,19 @@ msgstr "Intégrations"
msgid "Interviewing"
msgstr "Entretien"
#: src/views/members/components/InviteMemberForm.tsx:26
msgid "Invalid email address"
msgstr "Adresse e-mail invalide"
#: src/views/members/index.tsx:169
msgid "Invite"
msgstr "Inviter"
#: src/views/members/components/InviteMemberForm.tsx:82
#: src/views/members/components/InviteMemberForm.tsx:113
msgid "Invite another"
msgstr "Inviter un autre"
#: src/views/members/components/InviteMemberForm.tsx:123
#: src/views/card/components/MemberSelector.tsx:112
msgid "Invite member"
msgstr "Inviter un membre"
@@ -825,8 +922,8 @@ msgstr "Kanban réinventé"
msgid "Keep in mind that this action is irreversible."
msgstr "Gardez à l'esprit que cette action est irréversible."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/card/index.tsx:118
#: src/views/board/components/NewCardForm.tsx:398
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Étiquettes"
@@ -855,11 +952,11 @@ msgstr "Licence"
msgid "Light"
msgstr "Clair"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Lien copié"
#: src/views/card/index.tsx:105
#: src/views/card/index.tsx:110
msgid "List"
msgstr "Liste"
@@ -887,13 +984,22 @@ msgstr "Priorité basse"
msgid "magic link"
msgstr "lien magique"
#: src/views/card/components/ActivityList.tsx:75
msgid "marked a checklist item as incomplete"
msgstr "a marqué un élément de la checklist comme incomplet"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:196
msgid "marked checklist item <0>{0}</0> as incomplete"
msgstr "a marqué l'élément <0>{0}</0> de la checklist comme incomplet"
#: src/views/boards/components/TemplateBoards.tsx:17
msgid "Medium Priority"
msgstr "Priorité moyenne"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/card/index.tsx:126
#: src/views/board/components/NewCardForm.tsx:357
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -908,16 +1014,18 @@ msgstr "Membres | {0}"
msgid "Monthly"
msgstr "Mensuel"
#: src/views/card/components/ActivityList.tsx:81
msgid "moved the card from <0>{fromList}</0> to<1>{toList}</1>"
msgstr "a déplacé la carte de <0>{fromList}</0> vers<1>{toList}</1>"
#. placeholder {0}: truncate(fromList)
#. placeholder {1}: truncate(toList)
#: src/views/card/components/ActivityList.tsx:98
msgid "moved the card from <0>{0}</0> to<1>{1}</1>"
msgstr "a déplacé la carte de <0>{0}</0> vers <1>{1}</1>"
#: src/views/card/components/ActivityList.tsx:55
#: src/views/card/components/ActivityList.tsx:64
msgid "moved the card to another list"
msgstr "a déplacé la carte vers une autre liste"
#: src/views/boards/components/NewBoardForm.tsx:99
#: src/components/LabelForm.tsx:134
#: src/components/LabelForm.tsx:135
msgid "Name"
msgstr "Nom"
@@ -933,19 +1041,23 @@ msgstr "Nouveau"
msgid "New board"
msgstr "Nouveau tableau"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:290
msgid "New card"
msgstr "Nouvelle carte"
#: src/views/card/components/NewChecklistForm.tsx:102
msgid "New checklist"
msgstr "Nouvelle checklist"
#: src/views/boards/components/ImportBoardsForm.tsx:322
msgid "New import"
msgstr "Nouvel import"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "New label"
msgstr "Nouvelle étiquette"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:394
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "Nouvelle liste"
@@ -978,7 +1090,7 @@ msgstr "Aucun tableau trouvé"
msgid "No credit card required"
msgstr "Aucune carte de crédit requise"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:414
msgid "No lists"
msgstr "Aucune liste"
@@ -1059,24 +1171,32 @@ msgstr "Veuillez sélectionner un fichier à télécharger."
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
#: src/views/settings/components/Avatar.tsx:40
#: src/views/settings/components/Avatar.tsx:98
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
#: src/views/card/index.tsx:159
#: src/views/card/index.tsx:173
#: src/views/card/components/NewCommentForm.tsx:31
#: src/views/card/components/NewChecklistItemForm.tsx:89
#: src/views/card/components/NewChecklistForm.tsx:70
#: src/views/card/components/MemberSelector.tsx:80
#: src/views/card/components/ListSelector.tsx:53
#: src/views/card/components/LabelSelector.tsx:73
#: src/views/card/components/DeleteCommentConfirmation.tsx:45
#: src/views/card/components/DeleteChecklistConfirmation.tsx:37
#: src/views/card/components/DeleteCardConfirmation.tsx:52
#: src/views/card/components/Comment.tsx:62
#: src/views/card/components/Comment.tsx:64
#: src/views/card/components/ChecklistNameInput.tsx:46
#: src/views/card/components/ChecklistItemRow.tsx:60
#: src/views/card/components/ChecklistItemRow.tsx:88
#: src/views/boards/components/ImportBoardsForm.tsx:205
#: src/views/board/index.tsx:135
#: src/views/board/index.tsx:191
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:190
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:174
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
#: src/components/DeleteLabelConfirmation.tsx:26
msgid "Please try again later, or contact customer support."
msgstr "Veuillez réessayer plus tard ou contacter le service client."
@@ -1142,17 +1262,32 @@ msgstr "Supprimer"
msgid "Remove member"
msgstr "Supprimer le membre"
#: src/views/card/components/ActivityList.tsx:57
#: src/views/card/components/ActivityList.tsx:66
msgid "removed a label from the card"
msgstr "a supprimé une étiquette de la carte"
#: src/views/card/components/ActivityList.tsx:59
#: src/views/card/components/ActivityList.tsx:68
msgid "removed a member from the card"
msgstr "a retiré un membre de la carte"
#: src/views/card/components/ActivityList.tsx:118
msgid "removed label <0>{label}</0>"
msgstr "a supprimé l'étiquette <0>{label}</0>"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:138
msgid "removed label <0>{0}</0>"
msgstr "a supprimé l'étiquette <0>{0}</0>"
#: src/views/card/components/ActivityList.tsx:70
msgid "renamed a checklist"
msgstr "a renommé une checklist"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:154
msgid "renamed checklist <0>{0}</0>"
msgstr "a renommé la checklist <0>{0}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:178
msgid "renamed checklist item to <0>{0}</0>"
msgstr "a renommé l'élément de la liste de contrôle en <0>{0}</0>"
#: src/views/boards/components/TemplateBoards.tsx:76
msgid "Research"
@@ -1187,7 +1322,7 @@ msgstr "Rôle"
msgid "Run on your own infrastructure"
msgstr "Exécutez sur votre propre infrastructure"
#: src/views/card/components/Comment.tsx:163
#: src/views/card/components/Comment.tsx:165
msgid "Save"
msgstr "Enregistrer"
@@ -1215,7 +1350,7 @@ msgstr "Auto-hébergement"
msgid "Self host with Github"
msgstr "Auto-hébergement avec Github"
#: src/views/card/components/ActivityList.tsx:89
#: src/views/card/components/ActivityList.tsx:107
msgid "self-assigned the card"
msgstr "s'est auto-assigné la carte"
@@ -1334,10 +1469,16 @@ msgid "They won't be able to access this workspace."
msgstr "Ils ne pourront plus accéder à cet espace de travail."
#: src/views/card/components/DeleteCommentConfirmation.tsx:68
#: src/views/card/components/DeleteChecklistConfirmation.tsx:58
#: src/views/card/components/DeleteCardConfirmation.tsx:78
#: src/components/DeleteLabelConfirmation.tsx:44
msgid "This action can't be undone."
msgstr "Cette action ne peut pas être annulée."
#: src/views/public/board/index.tsx:146
msgid "This board is private or does not exist"
msgstr "Ce tableau est privé ou n'existe pas"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "Cette URL de tableau est déjà utilisée"
@@ -1379,14 +1520,22 @@ msgstr "Importations Trello"
msgid "Triaging"
msgstr "Triage"
#: src/views/card/components/NewChecklistItemForm.tsx:88
msgid "Unable to add checklist item"
msgstr "Impossible d'ajouter l'élément à la liste de contrôle"
#: src/views/card/components/NewCommentForm.tsx:30
msgid "Unable to add comment"
msgstr "Impossible d'ajouter un commentaire"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:171
msgid "Unable to create card"
msgstr "Impossible de créer la carte"
#: src/views/card/components/NewChecklistForm.tsx:69
msgid "Unable to create checklist"
msgstr "Impossible de créer la liste de contrôle"
#: src/views/board/components/NewListForm.tsx:77
msgid "Unable to create list"
msgstr "Impossible de créer une liste"
@@ -1399,6 +1548,14 @@ msgstr "Impossible de créer l'espace de travail"
msgid "Unable to delete card"
msgstr "Impossible de supprimer la carte"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:36
msgid "Unable to delete checklist"
msgstr "Impossible de supprimer la liste de contrôle"
#: src/views/card/components/ChecklistItemRow.tsx:87
msgid "Unable to delete checklist item"
msgstr "Impossible de supprimer l'élément de la liste de contrôle"
#: src/views/card/components/DeleteCommentConfirmation.tsx:44
msgid "Unable to delete comment"
msgstr "Impossible de supprimer le commentaire"
@@ -1419,12 +1576,20 @@ msgstr "Impossible de mettre à jour l'URL du tableau"
msgid "Unable to update board visibility"
msgstr "Impossible de mettre à jour la visibilité du tableau"
#: src/views/card/index.tsx:158
#: src/views/board/index.tsx:190
#: src/views/card/index.tsx:172
#: src/views/board/index.tsx:189
msgid "Unable to update card"
msgstr "Impossible de mettre à jour la carte"
#: src/views/card/components/Comment.tsx:61
#: src/views/card/components/ChecklistNameInput.tsx:45
msgid "Unable to update checklist"
msgstr "Impossible de mettre à jour la liste de contrôle"
#: src/views/card/components/ChecklistItemRow.tsx:59
msgid "Unable to update checklist item"
msgstr "Impossible de mettre à jour l'élément de la liste de contrôle"
#: src/views/card/components/Comment.tsx:63
msgid "Unable to update comment"
msgstr "Impossible de mettre à jour le commentaire"
@@ -1433,7 +1598,7 @@ msgid "Unable to update labels"
msgstr "Impossible de mettre à jour les étiquettes"
#: src/views/card/components/ListSelector.tsx:52
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:133
msgid "Unable to update list"
msgstr "Impossible de mettre à jour la liste"
@@ -1441,11 +1606,12 @@ msgstr "Impossible de mettre à jour la liste"
msgid "Unable to update members"
msgstr "Impossible de mettre à jour les membres"
#: src/views/card/components/ActivityList.tsx:102
msgid "unassigned <0>{memberName}</0> from the card"
msgstr "a retiré <0>{memberName}</0> de la carte"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:121
msgid "unassigned <0>{0}</0> from the card"
msgstr "a désassigné <0>{0}</0> de la carte"
#: src/views/card/components/ActivityList.tsx:99
#: src/views/card/components/ActivityList.tsx:118
msgid "unassigned themselves from the card"
msgstr "s'est retiré de la carte"
@@ -1477,25 +1643,30 @@ msgstr "Listes illimitées"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Mettre à jour"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:238
msgid "Update label"
msgstr "Mettre à jour l'étiquette"
#: src/views/card/components/ActivityList.tsx:54
#: src/views/card/components/ActivityList.tsx:73
msgid "updated a checklist item"
msgstr "a mis à jour un élément de la liste de contrôle"
#: src/views/card/components/ActivityList.tsx:63
msgid "updated the description"
msgstr "a mis à jour la description"
#: src/views/card/components/ActivityList.tsx:53
#: src/views/card/components/ActivityList.tsx:62
msgid "updated the title"
msgstr "a mis à jour le titre"
#: src/views/card/components/ActivityList.tsx:73
msgid "updated the title to <0>{toTitle}</0>"
msgstr "a mis à jour le titre en <0>{toTitle}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:90
msgid "updated the title to <0>{0}</0>"
msgstr "a mis à jour le titre en <0>{0}</0>"
#: src/views/settings/components/CustomURLConfirmation.tsx:52
msgid "Upgrade"
@@ -1526,6 +1697,10 @@ msgstr "Utiliser le modèle"
msgid "User"
msgstr "Utilisateur"
#: src/views/members/components/InviteMemberForm.tsx:58
msgid "User is already a member of this workspace"
msgstr "L'utilisateur est déjà membre de cet espace de travail"
#: src/views/boards/components/TemplateBoards.tsx:37
msgid "Video"
msgstr "Vidéo"
@@ -1542,10 +1717,18 @@ msgstr "Consultez et gérez votre facturation et votre abonnement."
msgid "View docs"
msgstr "Voir la documentation"
#: src/views/public/board/index.tsx:120
msgid "View only"
msgstr "Affichage uniquement"
#: src/views/home/components/Features.tsx:147
msgid "View our roadmap."
msgstr "Consultez notre feuille de route."
#: src/views/public/board/index.tsx:149
msgid "View workspace"
msgstr "Voir l'espace de travail"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Visibilité"
@@ -1578,7 +1761,7 @@ msgstr "Quand Trello a été lancé en 2011, il a impressionné tout le monde pa
msgid "Why make an open source Trello?"
msgstr "Pourquoi créer un Trello open source ?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Workspace"
msgstr "Espace de travail"

File diff suppressed because one or more lines are too long

View File

@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: src/views/card/components/Comment.tsx:121
#: src/views/card/components/Comment.tsx:123
msgid " (edited)"
msgstr " (modificato)"
@@ -23,12 +23,12 @@ msgstr ", o consulta i nostri"
#. placeholder {0}: card?.title ?? "Card"
#. placeholder {1}: board?.name ?? "Board"
#: src/views/card/index.tsx:189
#: src/views/card/index.tsx:214
msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:432
msgid "{0} labels"
msgstr "{0} etichette"
@@ -48,8 +48,8 @@ msgstr "Un'app kanban potente e flessibile che ti aiuta a organizzare il lavoro,
msgid "Account deleted"
msgstr "Account eliminato"
#: src/views/public/board/CardModal.tsx:104
#: src/views/card/index.tsx:256
#: src/views/public/board/CardModal.tsx:140
#: src/views/card/index.tsx:288
msgid "Activity"
msgstr "Attività"
@@ -62,35 +62,66 @@ msgid "Add a card"
msgstr "Aggiungi una carta"
#: src/views/card/components/NewCommentForm.tsx:57
#: src/views/card/components/Comment.tsx:144
#: src/views/card/components/Comment.tsx:146
msgid "Add a comment..."
msgstr "Aggiungi un commento..."
#: src/components/Editor.tsx:302
msgid "Add description... (type '/' to open commands)"
msgstr "Aggiungi descrizione... (digita '/' per aprire i comandi)"
#: src/views/card/components/NewChecklistItemForm.tsx:140
msgid "Add an item..."
msgstr "Aggiungi un elemento..."
#: src/views/card/components/Dropdown.tsx:18
msgid "Add checklist"
msgstr "Aggiungi checklist"
#: src/components/Editor.tsx:442
msgid "Add description... (type '/' to open commands or '@' to mention)"
msgstr "Aggiungi descrizione... (digita '/' per aprire i comandi o '@' per menzionare)"
#: src/views/card/components/ChecklistItemRow.tsx:172
msgid "Add details..."
msgstr "Aggiungi dettagli..."
#: src/views/card/components/LabelSelector.tsx:110
#: src/views/card/components/LabelSelector.tsx:118
msgid "Add label"
msgstr "Aggiungi etichetta"
#: src/views/members/components/InviteMemberForm.tsx:50
#: src/views/members/components/InviteMemberForm.tsx:85
#: src/views/card/components/MemberSelector.tsx:130
msgid "Add member"
msgstr "Aggiungi membro"
#: src/views/card/components/ActivityList.tsx:56
#: src/views/card/components/ActivityList.tsx:69
msgid "added a checklist"
msgstr "ha aggiunto una checklist"
#: src/views/card/components/ActivityList.tsx:72
msgid "added a checklist item"
msgstr "ha aggiunto un elemento alla checklist"
#: src/views/card/components/ActivityList.tsx:65
msgid "added a label to the card"
msgstr "ha aggiunto un'etichetta alla carta"
#: src/views/card/components/ActivityList.tsx:58
#: src/views/card/components/ActivityList.tsx:67
msgid "added a member to the card"
msgstr "ha aggiunto un membro alla carta"
#: src/views/card/components/ActivityList.tsx:110
msgid "added label <0>{label}</0>"
msgstr "ha aggiunto l'etichetta <0>{label}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:146
msgid "added checklist <0>{0}</0>"
msgstr "ha aggiunto la checklist <0>{0}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:170
msgid "added checklist item <0>{0}</0>"
msgstr "ha aggiunto l'elemento <0>{0}</0> alla checklist"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:130
msgid "added label <0>{0}</0>"
msgstr "ha aggiunto l'etichetta <0>{0}</0>"
#: src/views/home/components/Pricing.tsx:55
msgid "Admin roles"
@@ -141,17 +172,26 @@ msgstr "Sei sicuro di voler eliminare lo spazio di lavoro {0}?"
msgid "Are you sure you want to delete this card?"
msgstr "Sei sicuro di voler eliminare questa carta?"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:55
msgid "Are you sure you want to delete this checklist?"
msgstr "Sei sicuro di voler eliminare questa checklist?"
#: src/views/card/components/DeleteCommentConfirmation.tsx:65
msgid "Are you sure you want to delete this comment?"
msgstr "Sei sicuro di voler eliminare questo commento?"
#: src/components/DeleteLabelConfirmation.tsx:41
msgid "Are you sure you want to delete this label?"
msgstr "Sei sicuro di voler eliminare questa etichetta?"
#: src/views/settings/components/DeleteAccountConfirmation.tsx:54
msgid "Are you sure you want to delete your account?"
msgstr "Sei sicuro di voler eliminare il tuo account?"
#: src/views/card/components/ActivityList.tsx:92
msgid "assigned <0>{memberName}</0> to the card"
msgstr "ha assegnato <0>{memberName}</0> alla carta"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:110
msgid "assigned <0>{0}</0> to the card"
msgstr "ha assegnato <0>{0}</0> alla scheda"
#: src/views/boards/components/TemplateBoards.tsx:46
msgid "Awaiting Customer"
@@ -178,7 +218,7 @@ msgstr "Portale di fatturazione"
msgid "Blog Post"
msgstr "Post del blog"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Board"
msgstr "Bacheca"
@@ -190,7 +230,8 @@ msgstr "Il nome della bacheca non può superare i 100 caratteri"
msgid "Board name is required"
msgstr "Il nome della bacheca è obbligatorio"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:143
#: src/views/board/index.tsx:357
msgid "Board not found"
msgstr "Bacheca non trovata"
@@ -202,7 +243,7 @@ msgstr "L'URL della bacheca può contenere solo lettere, numeri e trattini"
msgid "Board URL cannot exceed 60 characters"
msgstr "L'URL della bacheca non può superare i 60 caratteri"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "URL della bacheca copiato negli appunti"
@@ -245,16 +286,18 @@ msgstr "Segnalazione bug"
#: src/views/settings/components/CustomURLConfirmation.tsx:50
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
#: src/views/card/components/DeleteCardConfirmation.tsx:86
#: src/views/card/components/Comment.tsx:156
#: src/views/card/components/Comment.tsx:158
#: src/components/DeleteLabelConfirmation.tsx:49
msgid "Cancel"
msgstr "Annulla"
#: src/views/card/index.tsx:234
#: src/views/card/index.tsx:259
msgid "Card not found"
msgstr "Carta non trovata"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:307
msgid "Card title"
msgstr "Titolo della carta"
@@ -267,6 +310,10 @@ msgstr "Cambia la lingua dell'app."
msgid "Check your inbox"
msgstr "Controlla la tua casella di posta"
#: src/views/card/components/NewChecklistForm.tsx:118
msgid "Checklist name"
msgstr "Nome della checklist"
#: src/views/board/components/Filters.tsx:155
msgid "Clear filters"
msgstr "Cancella filtri"
@@ -304,6 +351,15 @@ msgstr "Completato"
msgid "Complete control and ownership:"
msgstr "Controllo e proprietà completi:"
#: src/views/card/components/ActivityList.tsx:74
msgid "completed a checklist item"
msgstr "ha completato un elemento della checklist"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:187
msgid "completed checklist item <0>{0}</0>"
msgstr "ha completato l'elemento <0>{0}</0> della checklist"
#: src/views/settings/components/CustomURLConfirmation.tsx:42
msgid "Confirm URL change"
msgstr "Conferma modifica URL"
@@ -347,8 +403,8 @@ msgid "Control who can view and edit your boards."
msgstr "Controlla chi può visualizzare e modificare le tue bacheche."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/components/LabelForm.tsx:212
#: src/views/board/components/NewCardForm.tsx:458
#: src/components/LabelForm.tsx:213
msgid "Create another"
msgstr "Crea un altro"
@@ -356,15 +412,19 @@ msgstr "Crea un altro"
msgid "Create board"
msgstr "Crea bacheca"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:468
msgid "Create card"
msgstr "Crea carta"
#: src/components/LabelForm.tsx:233
#: src/views/card/components/NewChecklistForm.tsx:134
msgid "Create checklist"
msgstr "Crea checklist"
#: src/components/LabelForm.tsx:238
msgid "Create label"
msgstr "Crea etichetta"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Crea lista"
@@ -377,11 +437,11 @@ msgid "Create new key"
msgstr "Crea nuova chiave"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:394
msgid "Create new label"
msgstr "Crea nuova etichetta"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:425
msgid "Create new list"
msgstr "Crea nuova lista"
@@ -390,7 +450,7 @@ msgstr "Crea nuova lista"
msgid "Create workspace"
msgstr "Crea spazio di lavoro"
#: src/views/card/components/ActivityList.tsx:52
#: src/views/card/components/ActivityList.tsx:61
msgid "created the card"
msgstr "ha creato la carta"
@@ -415,8 +475,10 @@ msgid "Dark"
msgstr "Scuro"
#: src/views/card/components/DeleteCommentConfirmation.tsx:82
#: src/views/card/components/DeleteChecklistConfirmation.tsx:66
#: src/views/card/components/DeleteCardConfirmation.tsx:92
#: src/components/LabelForm.tsx:226
#: src/components/LabelForm.tsx:228
#: src/components/DeleteLabelConfirmation.tsx:51
msgid "Delete"
msgstr "Elimina"
@@ -430,11 +492,11 @@ msgstr "Elimina account"
msgid "Delete board"
msgstr "Elimina bacheca"
#: src/views/card/components/Dropdown.tsx:14
#: src/views/card/components/Dropdown.tsx:25
msgid "Delete card"
msgstr "Elimina carta"
#: src/views/card/components/Comment.tsx:89
#: src/views/card/components/Comment.tsx:91
msgid "Delete comment"
msgstr "Elimina commento"
@@ -448,6 +510,24 @@ msgstr "Elimina lista"
msgid "Delete workspace"
msgstr "Elimina spazio di lavoro"
#: src/views/card/components/ActivityList.tsx:71
msgid "deleted a checklist"
msgstr "ha eliminato una checklist"
#: src/views/card/components/ActivityList.tsx:76
msgid "deleted a checklist item"
msgstr "ha eliminato un elemento della checklist"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:162
msgid "deleted checklist <0>{0}</0>"
msgstr "ha eliminato la checklist <0>{0}</0>"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:205
msgid "deleted checklist item <0>{0}</0>"
msgstr "ha eliminato l'elemento <0>{0}</0> della checklist"
#: src/views/boards/components/TemplateBoards.tsx:32
msgid "Design"
msgstr "Design"
@@ -510,11 +590,11 @@ msgstr "Fatto"
msgid "Edit board URL"
msgstr "Modifica URL della bacheca"
#: src/views/card/components/Comment.tsx:80
#: src/views/card/components/Comment.tsx:82
msgid "Edit comment"
msgstr "Modifica commento"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "Edit label"
msgstr "Modifica etichetta"
@@ -530,7 +610,7 @@ msgstr "Modifica"
msgid "email"
msgstr "email"
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/InviteMemberForm.tsx:99
msgid "Email"
msgstr "Email"
@@ -554,6 +634,10 @@ msgstr "Inserisci la tua password"
msgid "Error deleting account"
msgstr "Errore durante l'eliminazione dell'account"
#: src/components/DeleteLabelConfirmation.tsx:25
msgid "Error deleting label"
msgstr "Errore durante l'eliminazione dell'etichetta"
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:45
msgid "Error deleting workspace"
msgstr "Errore durante l'eliminazione dell'area di lavoro"
@@ -562,6 +646,11 @@ msgstr "Errore durante l'eliminazione dell'area di lavoro"
msgid "Error disconnecting Trello"
msgstr "Errore durante la disconnessione da Trello"
#: src/views/members/components/InviteMemberForm.tsx:57
#: src/views/members/components/InviteMemberForm.tsx:63
msgid "Error inviting member"
msgstr "Errore durante l'invito del membro"
#: src/views/settings/components/UpdateDisplayNameForm.tsx:54
msgid "Error updating display name"
msgstr "Errore durante l'aggiornamento del nome visualizzato"
@@ -676,7 +765,7 @@ msgstr "Inizia ora"
msgid "Get started by creating a new board"
msgstr "Inizia creando una nuova bacheca"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:417
msgid "Get started by creating a new list"
msgstr "Inizia creando una nuova lista"
@@ -799,11 +888,19 @@ msgstr "Integrazioni"
msgid "Interviewing"
msgstr "Colloquio"
#: src/views/members/components/InviteMemberForm.tsx:26
msgid "Invalid email address"
msgstr "Indirizzo email non valido"
#: src/views/members/index.tsx:169
msgid "Invite"
msgstr "Invita"
#: src/views/members/components/InviteMemberForm.tsx:82
#: src/views/members/components/InviteMemberForm.tsx:113
msgid "Invite another"
msgstr "Invita un altro"
#: src/views/members/components/InviteMemberForm.tsx:123
#: src/views/card/components/MemberSelector.tsx:112
msgid "Invite member"
msgstr "Invita membro"
@@ -825,8 +922,8 @@ msgstr "Kanban reinventato"
msgid "Keep in mind that this action is irreversible."
msgstr "Tieni presente che questa azione è irreversibile."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/card/index.tsx:118
#: src/views/board/components/NewCardForm.tsx:398
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Etichette"
@@ -855,11 +952,11 @@ msgstr "Licenza"
msgid "Light"
msgstr "Chiaro"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Link copiato"
#: src/views/card/index.tsx:105
#: src/views/card/index.tsx:110
msgid "List"
msgstr "Lista"
@@ -887,13 +984,22 @@ msgstr "Bassa priorità"
msgid "magic link"
msgstr "link magico"
#: src/views/card/components/ActivityList.tsx:75
msgid "marked a checklist item as incomplete"
msgstr "ha segnato un elemento della checklist come incompleto"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:196
msgid "marked checklist item <0>{0}</0> as incomplete"
msgstr "ha segnato l'elemento <0>{0}</0> della checklist come incompleto"
#: src/views/boards/components/TemplateBoards.tsx:17
msgid "Medium Priority"
msgstr "Media priorità"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/card/index.tsx:126
#: src/views/board/components/NewCardForm.tsx:357
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -908,16 +1014,18 @@ msgstr "Membri | {0}"
msgid "Monthly"
msgstr "Mensile"
#: src/views/card/components/ActivityList.tsx:81
msgid "moved the card from <0>{fromList}</0> to<1>{toList}</1>"
msgstr "ha spostato la scheda da <0>{fromList}</0> a<1>{toList}</1>"
#. placeholder {0}: truncate(fromList)
#. placeholder {1}: truncate(toList)
#: src/views/card/components/ActivityList.tsx:98
msgid "moved the card from <0>{0}</0> to<1>{1}</1>"
msgstr "ha spostato la scheda da <0>{0}</0> a<1>{1}</1>"
#: src/views/card/components/ActivityList.tsx:55
#: src/views/card/components/ActivityList.tsx:64
msgid "moved the card to another list"
msgstr "ha spostato la scheda in un'altra lista"
#: src/views/boards/components/NewBoardForm.tsx:99
#: src/components/LabelForm.tsx:134
#: src/components/LabelForm.tsx:135
msgid "Name"
msgstr "Nome"
@@ -933,19 +1041,23 @@ msgstr "Nuovo"
msgid "New board"
msgstr "Nuova bacheca"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:290
msgid "New card"
msgstr "Nuova carta"
#: src/views/card/components/NewChecklistForm.tsx:102
msgid "New checklist"
msgstr "Nuova checklist"
#: src/views/boards/components/ImportBoardsForm.tsx:322
msgid "New import"
msgstr "Nuova importazione"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "New label"
msgstr "Nuova etichetta"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:394
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "Nuova lista"
@@ -978,7 +1090,7 @@ msgstr "Nessuna bacheca trovata"
msgid "No credit card required"
msgstr "Nessuna carta di credito richiesta"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:414
msgid "No lists"
msgstr "Nessuna lista"
@@ -1059,24 +1171,32 @@ msgstr "Seleziona un file da caricare."
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
#: src/views/settings/components/Avatar.tsx:40
#: src/views/settings/components/Avatar.tsx:98
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
#: src/views/card/index.tsx:159
#: src/views/card/index.tsx:173
#: src/views/card/components/NewCommentForm.tsx:31
#: src/views/card/components/NewChecklistItemForm.tsx:89
#: src/views/card/components/NewChecklistForm.tsx:70
#: src/views/card/components/MemberSelector.tsx:80
#: src/views/card/components/ListSelector.tsx:53
#: src/views/card/components/LabelSelector.tsx:73
#: src/views/card/components/DeleteCommentConfirmation.tsx:45
#: src/views/card/components/DeleteChecklistConfirmation.tsx:37
#: src/views/card/components/DeleteCardConfirmation.tsx:52
#: src/views/card/components/Comment.tsx:62
#: src/views/card/components/Comment.tsx:64
#: src/views/card/components/ChecklistNameInput.tsx:46
#: src/views/card/components/ChecklistItemRow.tsx:60
#: src/views/card/components/ChecklistItemRow.tsx:88
#: src/views/boards/components/ImportBoardsForm.tsx:205
#: src/views/board/index.tsx:135
#: src/views/board/index.tsx:191
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:190
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:174
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
#: src/components/DeleteLabelConfirmation.tsx:26
msgid "Please try again later, or contact customer support."
msgstr "Riprova più tardi o contatta l'assistenza clienti."
@@ -1142,17 +1262,32 @@ msgstr "Rimuovi"
msgid "Remove member"
msgstr "Rimuovi membro"
#: src/views/card/components/ActivityList.tsx:57
#: src/views/card/components/ActivityList.tsx:66
msgid "removed a label from the card"
msgstr "ha rimosso un'etichetta dalla scheda"
#: src/views/card/components/ActivityList.tsx:59
#: src/views/card/components/ActivityList.tsx:68
msgid "removed a member from the card"
msgstr "ha rimosso un membro dalla scheda"
#: src/views/card/components/ActivityList.tsx:118
msgid "removed label <0>{label}</0>"
msgstr "ha rimosso l'etichetta <0>{label}</0>"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:138
msgid "removed label <0>{0}</0>"
msgstr "ha rimosso l'etichetta <0>{0}</0>"
#: src/views/card/components/ActivityList.tsx:70
msgid "renamed a checklist"
msgstr "ha rinominato una checklist"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:154
msgid "renamed checklist <0>{0}</0>"
msgstr "ha rinominato la checklist <0>{0}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:178
msgid "renamed checklist item to <0>{0}</0>"
msgstr "ha rinominato l'elemento della checklist in <0>{0}</0>"
#: src/views/boards/components/TemplateBoards.tsx:76
msgid "Research"
@@ -1187,7 +1322,7 @@ msgstr "Ruolo"
msgid "Run on your own infrastructure"
msgstr "Esegui sulla tua infrastruttura"
#: src/views/card/components/Comment.tsx:163
#: src/views/card/components/Comment.tsx:165
msgid "Save"
msgstr "Salva"
@@ -1215,7 +1350,7 @@ msgstr "Self Host"
msgid "Self host with Github"
msgstr "Self host con Github"
#: src/views/card/components/ActivityList.tsx:89
#: src/views/card/components/ActivityList.tsx:107
msgid "self-assigned the card"
msgstr "si è auto-assegnato la scheda"
@@ -1334,10 +1469,16 @@ msgid "They won't be able to access this workspace."
msgstr "Non potranno accedere a questo spazio di lavoro."
#: src/views/card/components/DeleteCommentConfirmation.tsx:68
#: src/views/card/components/DeleteChecklistConfirmation.tsx:58
#: src/views/card/components/DeleteCardConfirmation.tsx:78
#: src/components/DeleteLabelConfirmation.tsx:44
msgid "This action can't be undone."
msgstr "Questa azione non può essere annullata."
#: src/views/public/board/index.tsx:146
msgid "This board is private or does not exist"
msgstr "Questa bacheca è privata o non esiste"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "Questo URL della bacheca è già stato utilizzato"
@@ -1379,14 +1520,22 @@ msgstr "Importazioni da Trello"
msgid "Triaging"
msgstr "Smistamento"
#: src/views/card/components/NewChecklistItemForm.tsx:88
msgid "Unable to add checklist item"
msgstr "Impossibile aggiungere l'elemento della checklist"
#: src/views/card/components/NewCommentForm.tsx:30
msgid "Unable to add comment"
msgstr "Impossibile aggiungere il commento"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:171
msgid "Unable to create card"
msgstr "Impossibile creare la scheda"
#: src/views/card/components/NewChecklistForm.tsx:69
msgid "Unable to create checklist"
msgstr "Impossibile creare la checklist"
#: src/views/board/components/NewListForm.tsx:77
msgid "Unable to create list"
msgstr "Impossibile creare la lista"
@@ -1399,6 +1548,14 @@ msgstr "Impossibile creare lo spazio di lavoro"
msgid "Unable to delete card"
msgstr "Impossibile eliminare la scheda"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:36
msgid "Unable to delete checklist"
msgstr "Impossibile eliminare la checklist"
#: src/views/card/components/ChecklistItemRow.tsx:87
msgid "Unable to delete checklist item"
msgstr "Impossibile eliminare l'elemento della checklist"
#: src/views/card/components/DeleteCommentConfirmation.tsx:44
msgid "Unable to delete comment"
msgstr "Impossibile eliminare il commento"
@@ -1419,12 +1576,20 @@ msgstr "Impossibile aggiornare l'URL della bacheca"
msgid "Unable to update board visibility"
msgstr "Impossibile aggiornare la visibilità della bacheca"
#: src/views/card/index.tsx:158
#: src/views/board/index.tsx:190
#: src/views/card/index.tsx:172
#: src/views/board/index.tsx:189
msgid "Unable to update card"
msgstr "Impossibile aggiornare la scheda"
#: src/views/card/components/Comment.tsx:61
#: src/views/card/components/ChecklistNameInput.tsx:45
msgid "Unable to update checklist"
msgstr "Impossibile aggiornare la checklist"
#: src/views/card/components/ChecklistItemRow.tsx:59
msgid "Unable to update checklist item"
msgstr "Impossibile aggiornare l'elemento della checklist"
#: src/views/card/components/Comment.tsx:63
msgid "Unable to update comment"
msgstr "Impossibile aggiornare il commento"
@@ -1433,7 +1598,7 @@ msgid "Unable to update labels"
msgstr "Impossibile aggiornare le etichette"
#: src/views/card/components/ListSelector.tsx:52
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:133
msgid "Unable to update list"
msgstr "Impossibile aggiornare la lista"
@@ -1441,11 +1606,12 @@ msgstr "Impossibile aggiornare la lista"
msgid "Unable to update members"
msgstr "Impossibile aggiornare i membri"
#: src/views/card/components/ActivityList.tsx:102
msgid "unassigned <0>{memberName}</0> from the card"
msgstr "ha rimosso l'assegnazione di <0>{memberName}</0> dalla scheda"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:121
msgid "unassigned <0>{0}</0> from the card"
msgstr "ha rimosso l'assegnazione di <0>{0}</0> dalla scheda"
#: src/views/card/components/ActivityList.tsx:99
#: src/views/card/components/ActivityList.tsx:118
msgid "unassigned themselves from the card"
msgstr "si è rimosso dall'assegnazione della scheda"
@@ -1477,25 +1643,30 @@ msgstr "Liste illimitate"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Aggiorna"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:238
msgid "Update label"
msgstr "Aggiorna etichetta"
#: src/views/card/components/ActivityList.tsx:54
#: src/views/card/components/ActivityList.tsx:73
msgid "updated a checklist item"
msgstr "ha aggiornato un elemento della checklist"
#: src/views/card/components/ActivityList.tsx:63
msgid "updated the description"
msgstr "ha aggiornato la descrizione"
#: src/views/card/components/ActivityList.tsx:53
#: src/views/card/components/ActivityList.tsx:62
msgid "updated the title"
msgstr "ha aggiornato il titolo"
#: src/views/card/components/ActivityList.tsx:73
msgid "updated the title to <0>{toTitle}</0>"
msgstr "ha aggiornato il titolo in <0>{toTitle}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:90
msgid "updated the title to <0>{0}</0>"
msgstr "ha aggiornato il titolo in <0>{0}</0>"
#: src/views/settings/components/CustomURLConfirmation.tsx:52
msgid "Upgrade"
@@ -1526,6 +1697,10 @@ msgstr "Usa template"
msgid "User"
msgstr "Utente"
#: src/views/members/components/InviteMemberForm.tsx:58
msgid "User is already a member of this workspace"
msgstr "L'utente è già membro di questo spazio di lavoro"
#: src/views/boards/components/TemplateBoards.tsx:37
msgid "Video"
msgstr "Video"
@@ -1542,10 +1717,18 @@ msgstr "Visualizza e gestisci la tua fatturazione e abbonamento."
msgid "View docs"
msgstr "Visualizza documenti"
#: src/views/public/board/index.tsx:120
msgid "View only"
msgstr "Solo visualizzazione"
#: src/views/home/components/Features.tsx:147
msgid "View our roadmap."
msgstr "Visualizza la nostra roadmap."
#: src/views/public/board/index.tsx:149
msgid "View workspace"
msgstr "Visualizza spazio di lavoro"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Visibilità"
@@ -1578,7 +1761,7 @@ msgstr "Quando Trello fu lanciato nel 2011, stupì tutti con la sua semplicità
msgid "Why make an open source Trello?"
msgstr "Perché creare un Trello open source?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Workspace"
msgstr "Spazio di lavoro"

File diff suppressed because one or more lines are too long

View File

@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: src/views/card/components/Comment.tsx:121
#: src/views/card/components/Comment.tsx:123
msgid " (edited)"
msgstr " (bewerkt)"
@@ -23,12 +23,12 @@ msgstr ", of bekijk onze"
#. placeholder {0}: card?.title ?? "Card"
#. placeholder {1}: board?.name ?? "Board"
#: src/views/card/index.tsx:189
#: src/views/card/index.tsx:214
msgid "{0} | {1}"
msgstr "{0} | {1}"
#. placeholder {0}: labelPublicIds.length
#: src/views/board/components/NewCardForm.tsx:365
#: src/views/board/components/NewCardForm.tsx:432
msgid "{0} labels"
msgstr "{0} labels"
@@ -48,8 +48,8 @@ msgstr "Een krachtige, flexibele kanban-app die je helpt werk te organiseren, vo
msgid "Account deleted"
msgstr "Account verwijderd"
#: src/views/public/board/CardModal.tsx:104
#: src/views/card/index.tsx:256
#: src/views/public/board/CardModal.tsx:140
#: src/views/card/index.tsx:288
msgid "Activity"
msgstr "Activiteit"
@@ -62,35 +62,66 @@ msgid "Add a card"
msgstr "Voeg een kaart toe"
#: src/views/card/components/NewCommentForm.tsx:57
#: src/views/card/components/Comment.tsx:144
#: src/views/card/components/Comment.tsx:146
msgid "Add a comment..."
msgstr "Voeg een reactie toe..."
#: src/components/Editor.tsx:302
msgid "Add description... (type '/' to open commands)"
msgstr "Voeg beschrijving toe... (typ '/' om commando's te openen)"
#: src/views/card/components/NewChecklistItemForm.tsx:140
msgid "Add an item..."
msgstr "Voeg een item toe..."
#: src/views/card/components/Dropdown.tsx:18
msgid "Add checklist"
msgstr "Checklist toevoegen"
#: src/components/Editor.tsx:442
msgid "Add description... (type '/' to open commands or '@' to mention)"
msgstr "Beschrijving toevoegen... (typ '/' om commando's te openen of '@' om te vermelden)"
#: src/views/card/components/ChecklistItemRow.tsx:172
msgid "Add details..."
msgstr "Details toevoegen..."
#: src/views/card/components/LabelSelector.tsx:110
#: src/views/card/components/LabelSelector.tsx:118
msgid "Add label"
msgstr "Label toevoegen"
#: src/views/members/components/InviteMemberForm.tsx:50
#: src/views/members/components/InviteMemberForm.tsx:85
#: src/views/card/components/MemberSelector.tsx:130
msgid "Add member"
msgstr "Lid toevoegen"
#: src/views/card/components/ActivityList.tsx:56
#: src/views/card/components/ActivityList.tsx:69
msgid "added a checklist"
msgstr "heeft een checklist toegevoegd"
#: src/views/card/components/ActivityList.tsx:72
msgid "added a checklist item"
msgstr "heeft een checklistitem toegevoegd"
#: src/views/card/components/ActivityList.tsx:65
msgid "added a label to the card"
msgstr "heeft een label aan de kaart toegevoegd"
#: src/views/card/components/ActivityList.tsx:58
#: src/views/card/components/ActivityList.tsx:67
msgid "added a member to the card"
msgstr "heeft een lid aan de kaart toegevoegd"
#: src/views/card/components/ActivityList.tsx:110
msgid "added label <0>{label}</0>"
msgstr "heeft label <0>{label}</0> toegevoegd"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:146
msgid "added checklist <0>{0}</0>"
msgstr "heeft checklist <0>{0}</0> toegevoegd"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:170
msgid "added checklist item <0>{0}</0>"
msgstr "heeft checklistitem <0>{0}</0> toegevoegd"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:130
msgid "added label <0>{0}</0>"
msgstr "heeft label <0>{0}</0> toegevoegd"
#: src/views/home/components/Pricing.tsx:55
msgid "Admin roles"
@@ -141,17 +172,26 @@ msgstr "Weet je zeker dat je de werkruimte {0} wilt verwijderen?"
msgid "Are you sure you want to delete this card?"
msgstr "Weet je zeker dat je deze kaart wilt verwijderen?"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:55
msgid "Are you sure you want to delete this checklist?"
msgstr "Weet je zeker dat je deze checklist wilt verwijderen?"
#: src/views/card/components/DeleteCommentConfirmation.tsx:65
msgid "Are you sure you want to delete this comment?"
msgstr "Weet je zeker dat je deze opmerking wilt verwijderen?"
#: src/components/DeleteLabelConfirmation.tsx:41
msgid "Are you sure you want to delete this label?"
msgstr "Weet je zeker dat je dit label wilt verwijderen?"
#: src/views/settings/components/DeleteAccountConfirmation.tsx:54
msgid "Are you sure you want to delete your account?"
msgstr "Weet je zeker dat je je account wilt verwijderen?"
#: src/views/card/components/ActivityList.tsx:92
msgid "assigned <0>{memberName}</0> to the card"
msgstr "heeft <0>{memberName}</0> aan de kaart toegewezen"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:110
msgid "assigned <0>{0}</0> to the card"
msgstr "heeft <0>{0}</0> aan de kaart toegewezen"
#: src/views/boards/components/TemplateBoards.tsx:46
msgid "Awaiting Customer"
@@ -178,7 +218,7 @@ msgstr "Factureringsportaal"
msgid "Blog Post"
msgstr "Blogbericht"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Board"
msgstr "Bord"
@@ -190,7 +230,8 @@ msgstr "Bordnaam mag niet langer zijn dan 100 tekens"
msgid "Board name is required"
msgstr "Bordnaam is verplicht"
#: src/views/board/index.tsx:267
#: src/views/public/board/index.tsx:143
#: src/views/board/index.tsx:357
msgid "Board not found"
msgstr "Bord niet gevonden"
@@ -202,7 +243,7 @@ msgstr "Board URL kan alleen letters, cijfers en koppeltekens bevatten"
msgid "Board URL cannot exceed 60 characters"
msgstr "Board URL mag niet langer zijn dan 60 tekens"
#: src/views/public/board/index.tsx:53
#: src/views/public/board/index.tsx:59
msgid "Board URL copied to clipboard"
msgstr "Board URL gekopieerd naar klembord"
@@ -245,16 +286,18 @@ msgstr "Bugrapport"
#: src/views/settings/components/CustomURLConfirmation.tsx:50
#: src/views/members/components/DeleteMemberConfirmation.tsx:55
#: src/views/card/components/DeleteCommentConfirmation.tsx:76
#: src/views/card/components/DeleteChecklistConfirmation.tsx:63
#: src/views/card/components/DeleteCardConfirmation.tsx:86
#: src/views/card/components/Comment.tsx:156
#: src/views/card/components/Comment.tsx:158
#: src/components/DeleteLabelConfirmation.tsx:49
msgid "Cancel"
msgstr "Annuleren"
#: src/views/card/index.tsx:234
#: src/views/card/index.tsx:259
msgid "Card not found"
msgstr "Kaart niet gevonden"
#: src/views/board/components/NewCardForm.tsx:254
#: src/views/board/components/NewCardForm.tsx:307
msgid "Card title"
msgstr "Kaarttitel"
@@ -267,6 +310,10 @@ msgstr "Wijzig de taal van de app."
msgid "Check your inbox"
msgstr "Controleer je inbox"
#: src/views/card/components/NewChecklistForm.tsx:118
msgid "Checklist name"
msgstr "Naam checklist"
#: src/views/board/components/Filters.tsx:155
msgid "Clear filters"
msgstr "Filters wissen"
@@ -304,6 +351,15 @@ msgstr "Voltooid"
msgid "Complete control and ownership:"
msgstr "Volledige controle en eigendom:"
#: src/views/card/components/ActivityList.tsx:74
msgid "completed a checklist item"
msgstr "heeft een checklistitem voltooid"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:187
msgid "completed checklist item <0>{0}</0>"
msgstr "heeft checklistitem <0>{0}</0> voltooid"
#: src/views/settings/components/CustomURLConfirmation.tsx:42
msgid "Confirm URL change"
msgstr "URL-wijziging bevestigen"
@@ -347,8 +403,8 @@ msgid "Control who can view and edit your boards."
msgstr "Bepaal wie je borden kan bekijken en bewerken."
#: src/views/board/components/NewListForm.tsx:140
#: src/views/board/components/NewCardForm.tsx:391
#: src/components/LabelForm.tsx:212
#: src/views/board/components/NewCardForm.tsx:458
#: src/components/LabelForm.tsx:213
msgid "Create another"
msgstr "Maak nog een"
@@ -356,15 +412,19 @@ msgstr "Maak nog een"
msgid "Create board"
msgstr "Maak bord"
#: src/views/board/components/NewCardForm.tsx:401
#: src/views/board/components/NewCardForm.tsx:468
msgid "Create card"
msgstr "Maak kaart"
#: src/components/LabelForm.tsx:233
#: src/views/card/components/NewChecklistForm.tsx:134
msgid "Create checklist"
msgstr "Checklist maken"
#: src/components/LabelForm.tsx:238
msgid "Create label"
msgstr "Maak label"
#: src/views/board/components/NewListForm.tsx:148
#: src/views/board/components/NewListForm.tsx:152
msgid "Create list"
msgstr "Lijst maken"
@@ -377,11 +437,11 @@ msgid "Create new key"
msgstr "Nieuwe sleutel aanmaken"
#: src/views/card/components/LabelSelector.tsx:98
#: src/views/board/components/NewCardForm.tsx:327
#: src/views/board/components/NewCardForm.tsx:394
msgid "Create new label"
msgstr "Maak nieuw label"
#: src/views/board/index.tsx:335
#: src/views/board/index.tsx:425
msgid "Create new list"
msgstr "Maak nieuwe lijst"
@@ -390,7 +450,7 @@ msgstr "Maak nieuwe lijst"
msgid "Create workspace"
msgstr "Werkruimte maken"
#: src/views/card/components/ActivityList.tsx:52
#: src/views/card/components/ActivityList.tsx:61
msgid "created the card"
msgstr "heeft de kaart aangemaakt"
@@ -415,8 +475,10 @@ msgid "Dark"
msgstr "Donker"
#: src/views/card/components/DeleteCommentConfirmation.tsx:82
#: src/views/card/components/DeleteChecklistConfirmation.tsx:66
#: src/views/card/components/DeleteCardConfirmation.tsx:92
#: src/components/LabelForm.tsx:226
#: src/components/LabelForm.tsx:228
#: src/components/DeleteLabelConfirmation.tsx:51
msgid "Delete"
msgstr "Verwijderen"
@@ -430,11 +492,11 @@ msgstr "Account verwijderen"
msgid "Delete board"
msgstr "Board verwijderen"
#: src/views/card/components/Dropdown.tsx:14
#: src/views/card/components/Dropdown.tsx:25
msgid "Delete card"
msgstr "Kaart verwijderen"
#: src/views/card/components/Comment.tsx:89
#: src/views/card/components/Comment.tsx:91
msgid "Delete comment"
msgstr "Reactie verwijderen"
@@ -448,6 +510,24 @@ msgstr "Lijst verwijderen"
msgid "Delete workspace"
msgstr "Werkruimte verwijderen"
#: src/views/card/components/ActivityList.tsx:71
msgid "deleted a checklist"
msgstr "heeft een checklist verwijderd"
#: src/views/card/components/ActivityList.tsx:76
msgid "deleted a checklist item"
msgstr "heeft een checklistitem verwijderd"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:162
msgid "deleted checklist <0>{0}</0>"
msgstr "heeft checklist <0>{0}</0> verwijderd"
#. placeholder {0}: truncate(fromTitle)
#: src/views/card/components/ActivityList.tsx:205
msgid "deleted checklist item <0>{0}</0>"
msgstr "heeft checklistitem <0>{0}</0> verwijderd"
#: src/views/boards/components/TemplateBoards.tsx:32
msgid "Design"
msgstr "Ontwerp"
@@ -510,11 +590,11 @@ msgstr "Klaar"
msgid "Edit board URL"
msgstr "Board URL bewerken"
#: src/views/card/components/Comment.tsx:80
#: src/views/card/components/Comment.tsx:82
msgid "Edit comment"
msgstr "Reactie bewerken"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "Edit label"
msgstr "Label bewerken"
@@ -530,7 +610,7 @@ msgstr "Bewerken"
msgid "email"
msgstr "e-mail"
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/InviteMemberForm.tsx:99
msgid "Email"
msgstr "E-mail"
@@ -554,6 +634,10 @@ msgstr "Voer je wachtwoord in"
msgid "Error deleting account"
msgstr "Fout bij verwijderen account"
#: src/components/DeleteLabelConfirmation.tsx:25
msgid "Error deleting label"
msgstr "Fout bij het verwijderen van label"
#: src/views/settings/components/DeleteWorkspaceConfirmation.tsx:45
msgid "Error deleting workspace"
msgstr "Fout bij verwijderen werkruimte"
@@ -562,6 +646,11 @@ msgstr "Fout bij verwijderen werkruimte"
msgid "Error disconnecting Trello"
msgstr "Fout bij ontkoppelen van Trello"
#: src/views/members/components/InviteMemberForm.tsx:57
#: src/views/members/components/InviteMemberForm.tsx:63
msgid "Error inviting member"
msgstr "Fout bij het uitnodigen van lid"
#: src/views/settings/components/UpdateDisplayNameForm.tsx:54
msgid "Error updating display name"
msgstr "Fout bij bijwerken weergavenaam"
@@ -676,7 +765,7 @@ msgstr "Aan de slag"
msgid "Get started by creating a new board"
msgstr "Begin door een nieuw bord te maken"
#: src/views/board/index.tsx:327
#: src/views/board/index.tsx:417
msgid "Get started by creating a new list"
msgstr "Begin door een nieuwe lijst te maken"
@@ -799,11 +888,19 @@ msgstr "Integraties"
msgid "Interviewing"
msgstr "Interviewen"
#: src/views/members/components/InviteMemberForm.tsx:26
msgid "Invalid email address"
msgstr "Ongeldig e-mailadres"
#: src/views/members/index.tsx:169
msgid "Invite"
msgstr "Uitnodigen"
#: src/views/members/components/InviteMemberForm.tsx:82
#: src/views/members/components/InviteMemberForm.tsx:113
msgid "Invite another"
msgstr "Nog iemand uitnodigen"
#: src/views/members/components/InviteMemberForm.tsx:123
#: src/views/card/components/MemberSelector.tsx:112
msgid "Invite member"
msgstr "Lid uitnodigen"
@@ -825,8 +922,8 @@ msgstr "Kanban opnieuw uitgevonden"
msgid "Keep in mind that this action is irreversible."
msgstr "Houd er rekening mee dat deze actie onomkeerbaar is."
#: src/views/card/index.tsx:113
#: src/views/board/components/NewCardForm.tsx:331
#: src/views/card/index.tsx:118
#: src/views/board/components/NewCardForm.tsx:398
#: src/views/board/components/Filters.tsx:101
msgid "Labels"
msgstr "Labels"
@@ -855,11 +952,11 @@ msgstr "Licentie"
msgid "Light"
msgstr "Licht"
#: src/views/public/board/index.tsx:51
#: src/views/public/board/index.tsx:57
msgid "Link copied"
msgstr "Link gekopieerd"
#: src/views/card/index.tsx:105
#: src/views/card/index.tsx:110
msgid "List"
msgstr "Lijst"
@@ -887,13 +984,22 @@ msgstr "Lage prioriteit"
msgid "magic link"
msgstr "magische link"
#: src/views/card/components/ActivityList.tsx:75
msgid "marked a checklist item as incomplete"
msgstr "heeft een checklistitem als onvoltooid gemarkeerd"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:196
msgid "marked checklist item <0>{0}</0> as incomplete"
msgstr "heeft checklistitem <0>{0}</0> als onvoltooid gemarkeerd"
#: src/views/boards/components/TemplateBoards.tsx:17
msgid "Medium Priority"
msgstr "Gemiddelde prioriteit"
#: src/views/members/index.tsx:161
#: src/views/card/index.tsx:121
#: src/views/board/components/NewCardForm.tsx:290
#: src/views/card/index.tsx:126
#: src/views/board/components/NewCardForm.tsx:357
#: src/views/board/components/Filters.tsx:93
#: src/components/SideNavigation.tsx:73
msgid "Members"
@@ -908,16 +1014,18 @@ msgstr "Leden | {0}"
msgid "Monthly"
msgstr "Maandelijks"
#: src/views/card/components/ActivityList.tsx:81
msgid "moved the card from <0>{fromList}</0> to<1>{toList}</1>"
msgstr "heeft de kaart verplaatst van <0>{fromList}</0> naar<1>{toList}</1>"
#. placeholder {0}: truncate(fromList)
#. placeholder {1}: truncate(toList)
#: src/views/card/components/ActivityList.tsx:98
msgid "moved the card from <0>{0}</0> to<1>{1}</1>"
msgstr "heeft de kaart verplaatst van <0>{0}</0> naar<1>{1}</1>"
#: src/views/card/components/ActivityList.tsx:55
#: src/views/card/components/ActivityList.tsx:64
msgid "moved the card to another list"
msgstr "heeft de kaart naar een andere lijst verplaatst"
#: src/views/boards/components/NewBoardForm.tsx:99
#: src/components/LabelForm.tsx:134
#: src/components/LabelForm.tsx:135
msgid "Name"
msgstr "Naam"
@@ -933,19 +1041,23 @@ msgstr "Nieuw"
msgid "New board"
msgstr "Nieuw bord"
#: src/views/board/components/NewCardForm.tsx:237
#: src/views/board/components/NewCardForm.tsx:290
msgid "New card"
msgstr "Nieuwe kaart"
#: src/views/card/components/NewChecklistForm.tsx:102
msgid "New checklist"
msgstr "Nieuwe checklist"
#: src/views/boards/components/ImportBoardsForm.tsx:322
msgid "New import"
msgstr "Nieuwe import"
#: src/components/LabelForm.tsx:118
#: src/components/LabelForm.tsx:119
msgid "New label"
msgstr "Nieuw label"
#: src/views/board/index.tsx:304
#: src/views/board/index.tsx:394
#: src/views/board/components/NewListForm.tsx:112
msgid "New list"
msgstr "Nieuwe lijst"
@@ -978,7 +1090,7 @@ msgstr "Geen borden gevonden"
msgid "No credit card required"
msgstr "Geen creditcard vereist"
#: src/views/board/index.tsx:324
#: src/views/board/index.tsx:414
msgid "No lists"
msgstr "Geen lijsten"
@@ -1059,24 +1171,32 @@ msgstr "Selecteer een bestand om te uploaden."
#: src/views/settings/components/DeleteAccountConfirmation.tsx:40
#: src/views/settings/components/Avatar.tsx:40
#: src/views/settings/components/Avatar.tsx:98
#: src/views/members/components/InviteMemberForm.tsx:64
#: src/views/members/components/DeleteMemberConfirmation.tsx:28
#: src/views/card/index.tsx:159
#: src/views/card/index.tsx:173
#: src/views/card/components/NewCommentForm.tsx:31
#: src/views/card/components/NewChecklistItemForm.tsx:89
#: src/views/card/components/NewChecklistForm.tsx:70
#: src/views/card/components/MemberSelector.tsx:80
#: src/views/card/components/ListSelector.tsx:53
#: src/views/card/components/LabelSelector.tsx:73
#: src/views/card/components/DeleteCommentConfirmation.tsx:45
#: src/views/card/components/DeleteChecklistConfirmation.tsx:37
#: src/views/card/components/DeleteCardConfirmation.tsx:52
#: src/views/card/components/Comment.tsx:62
#: src/views/card/components/Comment.tsx:64
#: src/views/card/components/ChecklistNameInput.tsx:46
#: src/views/card/components/ChecklistItemRow.tsx:60
#: src/views/card/components/ChecklistItemRow.tsx:88
#: src/views/boards/components/ImportBoardsForm.tsx:205
#: src/views/board/index.tsx:135
#: src/views/board/index.tsx:191
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:190
#: src/views/board/components/VisibilityButton.tsx:53
#: src/views/board/components/UpdateBoardSlugForm.tsx:72
#: src/views/board/components/NewListForm.tsx:78
#: src/views/board/components/NewCardForm.tsx:128
#: src/views/board/components/NewCardForm.tsx:174
#: src/components/NewWorkspaceForm.tsx:41
#: src/components/FeedbackModal.tsx:41
#: src/components/DeleteLabelConfirmation.tsx:26
msgid "Please try again later, or contact customer support."
msgstr "Probeer het later opnieuw of neem contact op met de klantenservice."
@@ -1142,17 +1262,32 @@ msgstr "Verwijderen"
msgid "Remove member"
msgstr "Lid verwijderen"
#: src/views/card/components/ActivityList.tsx:57
#: src/views/card/components/ActivityList.tsx:66
msgid "removed a label from the card"
msgstr "heeft een label van de kaart verwijderd"
#: src/views/card/components/ActivityList.tsx:59
#: src/views/card/components/ActivityList.tsx:68
msgid "removed a member from the card"
msgstr "heeft een lid van de kaart verwijderd"
#: src/views/card/components/ActivityList.tsx:118
msgid "removed label <0>{label}</0>"
msgstr "heeft label <0>{label}</0> verwijderd"
#. placeholder {0}: truncate(label)
#: src/views/card/components/ActivityList.tsx:138
msgid "removed label <0>{0}</0>"
msgstr "heeft label <0>{0}</0> verwijderd"
#: src/views/card/components/ActivityList.tsx:70
msgid "renamed a checklist"
msgstr "heeft een checklist hernoemd"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:154
msgid "renamed checklist <0>{0}</0>"
msgstr "heeft checklist <0>{0}</0> hernoemd"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:178
msgid "renamed checklist item to <0>{0}</0>"
msgstr "checklistitem hernoemd naar <0>{0}</0>"
#: src/views/boards/components/TemplateBoards.tsx:76
msgid "Research"
@@ -1187,7 +1322,7 @@ msgstr "Rol"
msgid "Run on your own infrastructure"
msgstr "Draai op je eigen infrastructuur"
#: src/views/card/components/Comment.tsx:163
#: src/views/card/components/Comment.tsx:165
msgid "Save"
msgstr "Opslaan"
@@ -1215,7 +1350,7 @@ msgstr "Zelf hosten"
msgid "Self host with Github"
msgstr "Zelf hosten met Github"
#: src/views/card/components/ActivityList.tsx:89
#: src/views/card/components/ActivityList.tsx:107
msgid "self-assigned the card"
msgstr "heeft de kaart aan zichzelf toegewezen"
@@ -1334,10 +1469,16 @@ msgid "They won't be able to access this workspace."
msgstr "Ze zullen geen toegang meer hebben tot deze werkruimte."
#: src/views/card/components/DeleteCommentConfirmation.tsx:68
#: src/views/card/components/DeleteChecklistConfirmation.tsx:58
#: src/views/card/components/DeleteCardConfirmation.tsx:78
#: src/components/DeleteLabelConfirmation.tsx:44
msgid "This action can't be undone."
msgstr "Deze actie kan niet ongedaan worden gemaakt."
#: src/views/public/board/index.tsx:146
msgid "This board is private or does not exist"
msgstr "Dit bord is privé of bestaat niet"
#: src/views/board/components/UpdateBoardSlugForm.tsx:135
msgid "This board URL has already been taken"
msgstr "Deze board URL is al in gebruik"
@@ -1379,14 +1520,22 @@ msgstr "Trello-imports"
msgid "Triaging"
msgstr "Triage"
#: src/views/card/components/NewChecklistItemForm.tsx:88
msgid "Unable to add checklist item"
msgstr "Kan checklistitem niet toevoegen"
#: src/views/card/components/NewCommentForm.tsx:30
msgid "Unable to add comment"
msgstr "Kan geen reactie toevoegen"
#: src/views/board/components/NewCardForm.tsx:125
#: src/views/board/components/NewCardForm.tsx:171
msgid "Unable to create card"
msgstr "Kan kaart niet aanmaken"
#: src/views/card/components/NewChecklistForm.tsx:69
msgid "Unable to create checklist"
msgstr "Kan checklist niet maken"
#: src/views/board/components/NewListForm.tsx:77
msgid "Unable to create list"
msgstr "Kan geen lijst maken"
@@ -1399,6 +1548,14 @@ msgstr "Kan werkruimte niet aanmaken"
msgid "Unable to delete card"
msgstr "Kan kaart niet verwijderen"
#: src/views/card/components/DeleteChecklistConfirmation.tsx:36
msgid "Unable to delete checklist"
msgstr "Kan checklist niet verwijderen"
#: src/views/card/components/ChecklistItemRow.tsx:87
msgid "Unable to delete checklist item"
msgstr "Kan checklistitem niet verwijderen"
#: src/views/card/components/DeleteCommentConfirmation.tsx:44
msgid "Unable to delete comment"
msgstr "Kan opmerking niet verwijderen"
@@ -1419,12 +1576,20 @@ msgstr "Kan board URL niet bijwerken"
msgid "Unable to update board visibility"
msgstr "Kan bordzichtbaarheid niet bijwerken"
#: src/views/card/index.tsx:158
#: src/views/board/index.tsx:190
#: src/views/card/index.tsx:172
#: src/views/board/index.tsx:189
msgid "Unable to update card"
msgstr "Kan kaart niet bijwerken"
#: src/views/card/components/Comment.tsx:61
#: src/views/card/components/ChecklistNameInput.tsx:45
msgid "Unable to update checklist"
msgstr "Kan checklist niet bijwerken"
#: src/views/card/components/ChecklistItemRow.tsx:59
msgid "Unable to update checklist item"
msgstr "Kan checklistitem niet bijwerken"
#: src/views/card/components/Comment.tsx:63
msgid "Unable to update comment"
msgstr "Kan reactie niet bijwerken"
@@ -1433,7 +1598,7 @@ msgid "Unable to update labels"
msgstr "Kan labels niet bijwerken"
#: src/views/card/components/ListSelector.tsx:52
#: src/views/board/index.tsx:134
#: src/views/board/index.tsx:133
msgid "Unable to update list"
msgstr "Kan lijst niet bijwerken"
@@ -1441,11 +1606,12 @@ msgstr "Kan lijst niet bijwerken"
msgid "Unable to update members"
msgstr "Kan leden niet bijwerken"
#: src/views/card/components/ActivityList.tsx:102
msgid "unassigned <0>{memberName}</0> from the card"
msgstr "<0>{memberName}</0> van de kaart verwijderd"
#. placeholder {0}: truncate(memberName)
#: src/views/card/components/ActivityList.tsx:121
msgid "unassigned <0>{0}</0> from the card"
msgstr "<0>{0}</0> van de kaart verwijderd"
#: src/views/card/components/ActivityList.tsx:99
#: src/views/card/components/ActivityList.tsx:118
msgid "unassigned themselves from the card"
msgstr "zichzelf van de kaart verwijderd"
@@ -1477,25 +1643,30 @@ msgstr "Onbeperkt aantal lijsten"
#: src/views/settings/components/UpdateWorkspaceNameForm.tsx:82
#: src/views/settings/components/UpdateWorkspaceDescriptionForm.tsx:90
#: src/views/settings/components/UpdateDisplayNameForm.tsx:79
#: src/views/board/components/UpdateBoardSlugForm.tsx:173
#: src/views/board/components/UpdateBoardSlugForm.tsx:174
msgid "Update"
msgstr "Bijwerken"
#: src/components/LabelForm.tsx:233
#: src/components/LabelForm.tsx:238
msgid "Update label"
msgstr "Label bijwerken"
#: src/views/card/components/ActivityList.tsx:54
#: src/views/card/components/ActivityList.tsx:73
msgid "updated a checklist item"
msgstr "een checklistitem bijgewerkt"
#: src/views/card/components/ActivityList.tsx:63
msgid "updated the description"
msgstr "beschrijving bijgewerkt"
#: src/views/card/components/ActivityList.tsx:53
#: src/views/card/components/ActivityList.tsx:62
msgid "updated the title"
msgstr "titel bijgewerkt"
#: src/views/card/components/ActivityList.tsx:73
msgid "updated the title to <0>{toTitle}</0>"
msgstr "titel bijgewerkt naar <0>{toTitle}</0>"
#. placeholder {0}: truncate(toTitle)
#: src/views/card/components/ActivityList.tsx:90
msgid "updated the title to <0>{0}</0>"
msgstr "titel bijgewerkt naar <0>{0}</0>"
#: src/views/settings/components/CustomURLConfirmation.tsx:52
msgid "Upgrade"
@@ -1526,6 +1697,10 @@ msgstr "Sjabloon gebruiken"
msgid "User"
msgstr "Gebruiker"
#: src/views/members/components/InviteMemberForm.tsx:58
msgid "User is already a member of this workspace"
msgstr "Gebruiker is al lid van deze werkruimte"
#: src/views/boards/components/TemplateBoards.tsx:37
msgid "Video"
msgstr "Video"
@@ -1542,10 +1717,18 @@ msgstr "Bekijk en beheer je facturering en abonnement."
msgid "View docs"
msgstr "Bekijk documentatie"
#: src/views/public/board/index.tsx:120
msgid "View only"
msgstr "Alleen bekijken"
#: src/views/home/components/Features.tsx:147
msgid "View our roadmap."
msgstr "Bekijk onze roadmap."
#: src/views/public/board/index.tsx:149
msgid "View workspace"
msgstr "Werkruimte bekijken"
#: src/views/board/components/VisibilityButton.tsx:91
msgid "Visibility"
msgstr "Zichtbaarheid"
@@ -1578,7 +1761,7 @@ msgstr "Toen Trello in 2011 werd gelanceerd, blies het iedereen omver met zijn z
msgid "Why make an open source Trello?"
msgstr "Waarom een open source Trello maken?"
#: src/views/board/index.tsx:241
#: src/views/board/index.tsx:331
msgid "Workspace"
msgstr "Werkruimte"

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,16 @@
import { createContext, useContext, useState } from "react";
type ModalContextType = {
interface ModalState {
contentType: string;
entityId?: string;
entityLabel?: string;
}
interface Props {
children: React.ReactNode;
}
interface ModalContextType {
isOpen: boolean;
openModal: (
contentType: string,
@@ -8,36 +18,80 @@ type ModalContextType = {
entityLabel?: string,
) => void;
closeModal: () => void;
closeModals: (count: number) => void;
clearAllModals: () => void;
modalContentType: string;
entityId: string;
entityLabel: string;
};
interface Props {
children: React.ReactNode;
modalStates: Record<string, any>;
setModalState: (modalType: string, state: any) => void;
getModalState: (modalType: string) => any;
clearModalState: (modalType: string) => void;
clearAllModalStates: () => void;
}
const ModalContext = createContext<ModalContextType | undefined>(undefined);
export const ModalProvider: React.FC<Props> = ({ children }) => {
const [isOpen, setIsOpen] = useState(false);
const [entityId, setEntityId] = useState("");
const [entityLabel, setEntityLabel] = useState("");
const [modalContentType, setModalContentType] = useState("");
const [modalStack, setModalStack] = useState<ModalState[]>([]);
const [modalStates, setModalStates] = useState<Record<string, any>>({});
const isOpen = modalStack.length > 0;
const currentModal = modalStack[modalStack.length - 1];
const modalContentType = currentModal?.contentType || "";
const entityId = currentModal?.entityId || "";
const entityLabel = currentModal?.entityLabel || "";
const openModal = (
contentType: string,
entityId?: string,
entityLabel?: string,
) => {
setIsOpen(true);
setModalContentType(contentType);
if (entityId) setEntityId(entityId);
if (entityLabel) setEntityLabel(entityLabel);
const newModal: ModalState = { contentType, entityId, entityLabel };
setModalStack((prev) => [...prev, newModal]);
};
const closeModal = () => {
setIsOpen(false);
setModalStack((prev) => {
if (prev.length <= 1) {
return [];
}
return prev.slice(0, -1);
});
};
const closeModals = (count: number) => {
setModalStack(prev => {
const newLength = Math.max(0, prev.length - count);
return prev.slice(0, newLength);
});
};
const clearAllModals = () => {
setModalStack([]);
};
const setModalState = (modalType: string, state: any) => {
setModalStates((prev) => ({
...prev,
[modalType]: state,
}));
};
const getModalState = (modalType: string) => {
return modalStates[modalType];
};
const clearModalState = (modalType: string) => {
setModalStates((prev) => {
const newStates = { ...prev };
delete newStates[modalType];
return newStates;
});
};
const clearAllModalStates = () => {
setModalStates({});
};
return (
@@ -46,9 +100,16 @@ export const ModalProvider: React.FC<Props> = ({ children }) => {
isOpen,
openModal,
closeModal,
closeModals,
clearAllModals,
modalContentType,
entityId,
entityLabel,
modalStates,
setModalState,
getModalState,
clearModalState,
clearAllModalStates,
}}
>
{children}

View File

@@ -45,10 +45,12 @@ export const formatMemberDisplayName = (
return localPart.replace(/[_-]/g, ".");
};
export const getAvatarUrl = (imageOrKey: string) => {
export const getAvatarUrl = (imageOrKey: string | null) => {
if (!imageOrKey) return "";
if (imageOrKey.startsWith("http://") || imageOrKey.startsWith("https://")) {
return imageOrKey;
}
return `${env("NEXT_PUBLIC_STORAGE_URL")}/${env("NEXT_PUBLIC_AVATAR_BUCKET_NAME")}/${imageOrKey}`;
};

View File

@@ -1,5 +1,6 @@
import Avatar from "~/components/Avatar";
import Badge from "~/components/Badge";
import CircularProgress from "~/components/CircularProgress";
import LabelIcon from "~/components/LabelIcon";
import { getAvatarUrl } from "~/utils/helpers";
@@ -7,6 +8,7 @@ const Card = ({
title,
labels,
members,
checklists,
}: {
title: string;
labels: { name: string; colourCode: string | null }[];
@@ -15,7 +17,28 @@ const Card = ({
email: string;
user: { name: string | null; email: string; image: string | null } | null;
}[];
checklists: {
publicId: string;
name: string;
items: {
publicId: string;
title: string;
completed: boolean;
index: number;
}[];
}[];
}) => {
const completedItems = checklists.reduce((acc, checklist) => {
return acc + checklist.items.filter((item) => item.completed).length;
}, 0);
const totalItems = checklists.reduce((acc, checklist) => {
return acc + checklist.items.length;
}, 0);
const progress =
totalItems > 0 ? Math.round((completedItems / totalItems) * 100) : 0;
return (
<div className="flex flex-col rounded-md border border-light-200 bg-light-50 px-3 py-2 text-sm text-neutral-900 dark:border-dark-200 dark:bg-dark-200 dark:text-dark-1000 dark:hover:bg-dark-300">
<span>{title}</span>
@@ -29,21 +52,37 @@ const Card = ({
/>
))}
</div>
<div className="isolate flex justify-end -space-x-1 overflow-hidden">
{members.map(({ user, email }) => {
const avatarUrl = user?.image
? getAvatarUrl(user.image)
: undefined;
return (
<Avatar
name={user?.name ?? ""}
email={user?.email ?? email}
imageUrl={avatarUrl}
<div className="flex items-center justify-end gap-1">
{checklists.length > 0 && (
<div className="flex items-center gap-1 rounded-full border-[1px] border-light-300 px-2 py-1 dark:border-dark-600">
<CircularProgress
progress={progress || 2}
size="sm"
className="flex-shrink-0"
/>
);
})}
<span className="text-[10px] text-light-900 dark:text-dark-950">
{completedItems}/{totalItems}
</span>
</div>
)}
{members.length > 0 && (
<div className="isolate flex justify-end -space-x-1 overflow-hidden">
{members.map(({ user, email }) => {
const avatarUrl = user?.image
? getAvatarUrl(user.image)
: undefined;
return (
<Avatar
name={user?.name ?? ""}
email={user?.email ?? email}
imageUrl={avatarUrl}
size="sm"
/>
);
})}
</div>
)}
</div>
</div>
) : null}

View File

@@ -14,10 +14,11 @@ import { generateUID } from "@kan/shared/utils";
import Avatar from "~/components/Avatar";
import Button from "~/components/Button";
import CheckboxDropdown from "~/components/CheckboxDropdown";
import Editor from "~/components/Editor";
import Editor, { WorkspaceMember } from "~/components/Editor";
import Input from "~/components/Input";
import LabelIcon from "~/components/LabelIcon";
import Toggle from "~/components/Toggle";
import { useModalFormState } from "~/hooks/useModalFormState";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
@@ -45,21 +46,28 @@ export function NewCardForm({
queryParams,
}: NewCardFormProps) {
const { showPopup } = usePopup();
const { closeModal, openModal } = useModal();
const { closeModal, openModal, modalStates, clearModalState } = useModal();
const utils = api.useUtils();
// persists the form values
const { formState, saveFormState } = useModalFormState<NewCardFormInput>({
modalType: "NEW_CARD",
initialValues: {
title: "",
description: "",
listPublicId,
labelPublicIds: [],
memberPublicIds: [],
isCreateAnotherEnabled: false,
position: "start",
},
resetOnClose: true,
});
const { register, handleSubmit, reset, setValue, watch } =
useForm<NewCardFormInput>({
defaultValues: {
title: "",
description: "",
listPublicId,
labelPublicIds: [],
memberPublicIds: [],
isCreateAnotherEnabled: false,
position: "start",
},
values: formState,
});
const labelPublicIds = watch("labelPublicIds") || [];
@@ -67,11 +75,49 @@ export function NewCardForm({
const isCreateAnotherEnabled = watch("isCreateAnotherEnabled");
const position = watch("position");
const title = watch("title");
const description = watch("description");
// saving form state whenever form values change
useEffect(() => {
const subscription = watch((data) => {
saveFormState(data as NewCardFormInput);
});
return () => subscription.unsubscribe();
}, [watch, saveFormState]);
const { data: boardData } = api.board.byId.useQuery(queryParams, {
enabled: !!boardPublicId,
});
// this adds the new created label to selected labels
useEffect(() => {
const newLabelId = modalStates["NEW_LABEL_CREATED"];
if (newLabelId !== undefined && !labelPublicIds.includes(newLabelId)) {
setValue("labelPublicIds", [...labelPublicIds, newLabelId]);
}
}, [modalStates, labelPublicIds]);
// this removes the deleted label from selected labels if it is selected
useEffect(() => {
if (boardData?.labels) {
const availableLabelIds = boardData.labels.map(label => label.publicId);
const newLabelId = modalStates["NEW_LABEL_CREATED"];
if (newLabelId && availableLabelIds.includes(newLabelId)) {
clearModalState("NEW_LABEL_CREATED");
}
const validLabelIds = labelPublicIds.filter(id =>
availableLabelIds.includes(id) || id === newLabelId
);
if (validLabelIds.length !== labelPublicIds.length) {
setValue("labelPublicIds", validLabelIds);
}
}
}, [boardData?.labels, labelPublicIds, modalStates["NEW_LABEL_CREATED"]]);
const createCard = api.card.create.useMutation({
onMutate: async (args) => {
await utils.board.byId.cancel();
@@ -131,17 +177,24 @@ export function NewCardForm({
},
onSuccess: async () => {
const isCreateAnotherEnabled = watch("isCreateAnotherEnabled");
if (!isCreateAnotherEnabled) closeModal();
if (!isCreateAnotherEnabled) {
// close modal (state will auto-clear due to resetOnClose: true)
closeModal();
} else {
// reset form for creating another card
const newFormState = {
title: "",
description: "",
listPublicId: watch("listPublicId"),
labelPublicIds: [],
memberPublicIds: [],
isCreateAnotherEnabled,
position,
};
reset(newFormState);
saveFormState(newFormState);
}
await utils.board.byId.invalidate(queryParams);
reset({
title: "",
description: "",
listPublicId: watch("listPublicId"),
labelPublicIds: [],
memberPublicIds: [],
isCreateAnotherEnabled,
position,
});
},
});
@@ -264,8 +317,22 @@ export function NewCardForm({
<div className="mt-2">
<div className="block max-h-48 min-h-24 w-full overflow-y-auto rounded-md border-0 bg-dark-300 bg-white/5 px-3 py-2 text-sm shadow-sm ring-1 ring-inset ring-light-600 focus-within:ring-2 focus-within:ring-inset focus-within:ring-light-700 dark:ring-dark-700 dark:focus-within:ring-dark-700 sm:leading-6">
<Editor
content=""
onChange={(value) => setValue("description", value)}
content={description}
onChange={(value) => {
setValue("description", value);
saveFormState({ ...formState, description: value });
}}
workspaceMembers={
boardData?.workspace.members?.map((member): WorkspaceMember => ({
publicId: member.publicId,
email: member.email,
user: member.user ? {
id: member.publicId,
name: member.user.name,
image: member.user.image ?? null,
} : null,
})) ?? []
}
/>
</div>
</div>

View File

@@ -145,7 +145,12 @@ export function NewListForm({
/>
<div>
<Button type="submit">{t`Create list`}</Button>
<Button
type="submit"
disabled={createList.isPending || !watch("name")}
>
{t`Create list`}
</Button>
</div>
</div>
</form>

View File

@@ -167,7 +167,8 @@ export function UpdateBoardSlugForm({
!isDirty ||
updateBoardSlug.isPending ||
errors.slug?.message !== undefined ||
isBoardSlugAvailable?.isReserved
isBoardSlugAvailable?.isReserved ||
checkBoardSlugAvailability.isLoading
}
>
{t`Update`}

View File

@@ -13,7 +13,6 @@ import type { UpdateBoardInput } from "@kan/api/types";
import Button from "~/components/Button";
import { DeleteLabelConfirmation } from "~/components/DeleteLabelConfirmation";
import FeedbackModal from "~/components/FeedbackModal";
import { LabelForm } from "~/components/LabelForm";
import Modal from "~/components/modal";
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
@@ -45,7 +44,7 @@ export default function BoardPage() {
const utils = api.useUtils();
const { showPopup } = usePopup();
const { workspace } = useWorkspace();
const { openModal, modalContentType, entityId } = useModal();
const { openModal, modalContentType, entityId, isOpen } = useModal();
const [selectedPublicListId, setSelectedPublicListId] =
useState<PublicListId>("");
const [isInitialLoading, setIsInitialLoading] = useState(true);
@@ -235,6 +234,97 @@ export default function BoardPage() {
}
};
const renderModalContent = () => {
return (
<>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "DELETE_BOARD"}
>
<DeleteBoardConfirmation boardPublicId={boardId ?? ""} />
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "DELETE_LIST"}
>
<DeleteListConfirmation
listPublicId={selectedPublicListId}
queryParams={queryParams}
/>
</Modal>
<Modal
modalSize="md"
isVisible={isOpen && modalContentType === "NEW_CARD"}
>
<NewCardForm
boardPublicId={boardId ?? ""}
listPublicId={selectedPublicListId}
queryParams={queryParams}
/>
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "NEW_LIST"}
>
<NewListForm
boardPublicId={boardId ?? ""}
queryParams={queryParams}
/>
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "NEW_WORKSPACE"}
>
<NewWorkspaceForm />
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "NEW_LABEL"}
>
<LabelForm boardPublicId={boardId ?? ""} refetch={refetchBoard} />
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "EDIT_LABEL"}
>
<LabelForm
boardPublicId={boardId ?? ""}
refetch={refetchBoard}
isEdit
/>
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "DELETE_LABEL"}
>
<DeleteLabelConfirmation
refetch={refetchBoard}
labelPublicId={entityId}
/>
</Modal>
<Modal
modalSize="sm"
isVisible={isOpen && modalContentType === "UPDATE_BOARD_SLUG"}
>
<UpdateBoardSlugForm
boardPublicId={boardId ?? ""}
workspaceSlug={workspace.slug ?? ""}
boardSlug={boardData?.slug ?? ""}
queryParams={queryParams}
/>
</Modal>
</>
);
};
return (
<>
<PageHead
@@ -401,6 +491,7 @@ export default function BoardPage() {
title={card.title}
labels={card.labels}
members={card.members}
checklists={card.checklists ?? []}
/>
</Link>
)}
@@ -422,63 +513,7 @@ export default function BoardPage() {
</>
) : null}
</div>
<Modal
modalSize={
modalContentType === "NEW_CARD" ||
modalContentType === "NEW_FEEDBACK"
? "md"
: "sm"
}
>
{modalContentType === "NEW_FEEDBACK" && <FeedbackModal />}
{modalContentType === "DELETE_BOARD" && (
<DeleteBoardConfirmation boardPublicId={boardId ?? ""} />
)}
{modalContentType === "DELETE_LIST" && (
<DeleteListConfirmation
listPublicId={selectedPublicListId}
queryParams={queryParams}
/>
)}
{modalContentType === "NEW_CARD" && (
<NewCardForm
boardPublicId={boardId ?? ""}
listPublicId={selectedPublicListId}
queryParams={queryParams}
/>
)}
{modalContentType === "NEW_LIST" && (
<NewListForm
boardPublicId={boardId ?? ""}
queryParams={queryParams}
/>
)}
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
{modalContentType === "NEW_LABEL" && (
<LabelForm boardPublicId={boardId ?? ""} refetch={refetchBoard} />
)}
{modalContentType === "EDIT_LABEL" && (
<LabelForm
boardPublicId={boardId ?? ""}
refetch={refetchBoard}
isEdit
/>
)}
{modalContentType === "DELETE_LABEL" && (
<DeleteLabelConfirmation
refetch={refetchBoard}
labelPublicId={entityId}
/>
)}
{modalContentType === "UPDATE_BOARD_SLUG" && (
<UpdateBoardSlugForm
boardPublicId={boardId ?? ""}
workspaceSlug={workspace.slug ?? ""}
boardSlug={boardData?.slug ?? ""}
queryParams={queryParams}
/>
)}
</Modal>
{renderModalContent()}
</div>
</>
);

View File

@@ -5,9 +5,11 @@ import { de, enGB, es, fr, it, nl } from "date-fns/locale";
import {
HiOutlineArrowLeft,
HiOutlineArrowRight,
HiOutlineCheckCircle,
HiOutlinePencil,
HiOutlinePlus,
HiOutlineTag,
HiOutlineTrash,
HiOutlineUserMinus,
HiOutlineUserPlus,
} from "react-icons/hi2";
@@ -31,6 +33,11 @@ const dateLocaleMap = {
nl: nl,
} as const;
const truncate = (value: string | null, maxLength = 50) => {
if (!value) return value;
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}` : value;
};
const getActivityText = ({
type,
toTitle,
@@ -39,6 +46,7 @@ const getActivityText = ({
memberName,
isSelf,
label,
fromTitle,
}: {
type: ActivityType;
toTitle: string | null;
@@ -47,6 +55,7 @@ const getActivityText = ({
memberName: string | null;
isSelf: boolean;
label: string | null;
fromTitle?: string | null;
}) => {
const ACTIVITY_TYPE_MAP = {
"card.created": t`created the card`,
@@ -57,6 +66,14 @@ const getActivityText = ({
"card.updated.label.removed": t`removed a label from the card`,
"card.updated.member.added": t`added a member to the card`,
"card.updated.member.removed": t`removed a member from the card`,
"card.updated.checklist.added": t`added a checklist`,
"card.updated.checklist.renamed": t`renamed a checklist`,
"card.updated.checklist.deleted": t`deleted a checklist`,
"card.updated.checklist.item.added": t`added a checklist item`,
"card.updated.checklist.item.updated": t`updated a checklist item`,
"card.updated.checklist.item.completed": t`completed a checklist item`,
"card.updated.checklist.item.uncompleted": t`marked a checklist item as incomplete`,
"card.updated.checklist.item.deleted": t`deleted a checklist item`,
} as const;
if (!(type in ACTIVITY_TYPE_MAP)) return null;
@@ -71,7 +88,7 @@ const getActivityText = ({
if (type === "card.updated.title" && toTitle) {
return (
<Trans>
updated the title to <TextHighlight>{toTitle}</TextHighlight>
updated the title to <TextHighlight>{truncate(toTitle)}</TextHighlight>
</Trans>
);
}
@@ -79,8 +96,9 @@ const getActivityText = ({
if (type === "card.updated.list" && fromList && toList) {
return (
<Trans>
moved the card from <TextHighlight>{fromList}</TextHighlight> to
<TextHighlight>{toList}</TextHighlight>
moved the card from <TextHighlight>{truncate(fromList)}</TextHighlight>{" "}
to
<TextHighlight>{truncate(toList)}</TextHighlight>
</Trans>
);
}
@@ -90,7 +108,8 @@ const getActivityText = ({
return (
<Trans>
assigned <TextHighlight>{memberName}</TextHighlight> to the card
assigned <TextHighlight>{truncate(memberName)}</TextHighlight> to the
card
</Trans>
);
}
@@ -100,7 +119,8 @@ const getActivityText = ({
return (
<Trans>
unassigned <TextHighlight>{memberName}</TextHighlight> from the card
unassigned <TextHighlight>{truncate(memberName)}</TextHighlight> from
the card
</Trans>
);
}
@@ -108,7 +128,7 @@ const getActivityText = ({
if (type === "card.updated.label.added" && label) {
return (
<Trans>
added label <TextHighlight>{label}</TextHighlight>
added label <TextHighlight>{truncate(label)}</TextHighlight>
</Trans>
);
}
@@ -116,7 +136,75 @@ const getActivityText = ({
if (type === "card.updated.label.removed" && label) {
return (
<Trans>
removed label <TextHighlight>{label}</TextHighlight>
removed label <TextHighlight>{truncate(label)}</TextHighlight>
</Trans>
);
}
if (type === "card.updated.checklist.added" && toTitle) {
return (
<Trans>
added checklist <TextHighlight>{truncate(toTitle)}</TextHighlight>
</Trans>
);
}
if (type === "card.updated.checklist.renamed" && toTitle) {
return (
<Trans>
renamed checklist <TextHighlight>{truncate(toTitle)}</TextHighlight>
</Trans>
);
}
if (type === "card.updated.checklist.deleted" && fromTitle) {
return (
<Trans>
deleted checklist <TextHighlight>{truncate(fromTitle)}</TextHighlight>
</Trans>
);
}
if (type === "card.updated.checklist.item.added" && toTitle) {
return (
<Trans>
added checklist item <TextHighlight>{truncate(toTitle)}</TextHighlight>
</Trans>
);
}
if (type === "card.updated.checklist.item.updated" && toTitle) {
return (
<Trans>
renamed checklist item to{" "}
<TextHighlight>{truncate(toTitle)}</TextHighlight>
</Trans>
);
}
if (type === "card.updated.checklist.item.completed" && toTitle) {
return (
<Trans>
completed checklist item{" "}
<TextHighlight>{truncate(toTitle)}</TextHighlight>
</Trans>
);
}
if (type === "card.updated.checklist.item.uncompleted" && toTitle) {
return (
<Trans>
marked checklist item <TextHighlight>{truncate(toTitle)}</TextHighlight>{" "}
as incomplete
</Trans>
);
}
if (type === "card.updated.checklist.item.deleted" && fromTitle) {
return (
<Trans>
deleted checklist item{" "}
<TextHighlight>{truncate(fromTitle)}</TextHighlight>
</Trans>
);
}
@@ -133,6 +221,14 @@ const ACTIVITY_ICON_MAP: Partial<Record<ActivityType, React.ReactNode | null>> =
"card.updated.label.removed": <HiOutlineTag />,
"card.updated.member.added": <HiOutlineUserPlus />,
"card.updated.member.removed": <HiOutlineUserMinus />,
"card.updated.checklist.added": <HiOutlinePlus />,
"card.updated.checklist.renamed": <HiOutlinePencil />,
"card.updated.checklist.deleted": <HiOutlineTrash />,
"card.updated.checklist.item.added": <HiOutlinePlus />,
"card.updated.checklist.item.updated": <HiOutlinePencil />,
"card.updated.checklist.item.completed": <HiOutlineCheckCircle />,
"card.updated.checklist.item.uncompleted": <HiOutlineCheckCircle />,
"card.updated.checklist.item.deleted": <HiOutlineTrash />,
} as const;
const getActivityIcon = (
@@ -155,11 +251,13 @@ const ActivityList = ({
cardPublicId,
isLoading,
isAdmin,
isViewOnly,
}: {
activities: NonNullable<GetCardByIdOutput>["activities"];
cardPublicId: string;
isLoading: boolean;
isAdmin?: boolean;
isViewOnly?: boolean;
}) => {
const { data } = authClient.useSession();
const { locale } = useLocalisation();
@@ -177,6 +275,7 @@ const ActivityList = ({
memberName: activity.member?.user?.name ?? null,
isSelf: activity.member?.user?.id === data?.user.id,
label: activity.label?.name ?? null,
fromTitle: activity.fromTitle ?? null,
});
if (activity.type === "card.updated.comment.added")
@@ -193,6 +292,7 @@ const ActivityList = ({
isEdited={!!activity.comment?.updatedAt}
isAuthor={activity.comment?.createdBy === data?.user.id}
isAdmin={isAdmin ?? false}
isViewOnly={!!isViewOnly}
/>
);

View File

@@ -0,0 +1,197 @@
import { t } from "@lingui/core/macro";
import { useEffect, useState } from "react";
import ContentEditable from "react-contenteditable";
import { HiXMark } from "react-icons/hi2";
import { twMerge } from "tailwind-merge";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
interface ChecklistItemRowProps {
item: {
publicId: string;
title: string;
completed: boolean;
};
cardPublicId: string;
viewOnly?: boolean;
}
export default function ChecklistItemRow({
item,
cardPublicId,
viewOnly = false,
}: ChecklistItemRowProps) {
const utils = api.useUtils();
const { showPopup } = usePopup();
const [title, setTitle] = useState("");
const [completed, setCompleted] = useState(false);
const updateItem = api.checklist.updateItem.useMutation({
onMutate: async (vars) => {
await utils.card.byId.cancel({ cardPublicId });
const previous = utils.card.byId.getData({ cardPublicId });
utils.card.byId.setData({ cardPublicId }, (old) => {
if (!old) return old as any;
const updatedChecklists = old.checklists.map((cl) => ({
...cl,
items: cl.items.map((ci) =>
ci.publicId === item.publicId
? {
...ci,
...(vars.title !== undefined ? { title: vars.title } : {}),
...(vars.completed !== undefined
? { completed: vars.completed }
: {}),
}
: ci,
),
}));
return { ...old, checklists: updatedChecklists } as typeof old;
});
return { previous };
},
onError: (_err, _vars, ctx) => {
if (ctx?.previous)
utils.card.byId.setData({ cardPublicId }, ctx.previous);
showPopup({
header: t`Unable to update checklist item`,
message: t`Please try again later, or contact customer support.`,
icon: "error",
});
},
onSettled: async () => {
await utils.card.byId.invalidate({ cardPublicId });
},
});
const deleteItem = api.checklist.deleteItem.useMutation({
onMutate: async () => {
await utils.card.byId.cancel({ cardPublicId });
const previous = utils.card.byId.getData({ cardPublicId });
utils.card.byId.setData({ cardPublicId }, (old) => {
if (!old) return old as any;
const updatedChecklists = old.checklists.map((cl) => ({
...cl,
items: cl.items.filter((ci) => ci.publicId !== item.publicId),
}));
return { ...old, checklists: updatedChecklists } as typeof old;
});
return { previous };
},
onError: (_err, _vars, ctx) => {
if (ctx?.previous)
utils.card.byId.setData({ cardPublicId }, ctx.previous);
showPopup({
header: t`Unable to delete checklist item`,
message: t`Please try again later, or contact customer support.`,
icon: "error",
});
},
onSettled: async () => {
await utils.card.byId.invalidate({ cardPublicId });
},
});
// Only resync from props when switching items to avoid clobbering edits
useEffect(() => {
setTitle(item.title);
setCompleted(item.completed);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [item.publicId]);
const sanitizeHtmlToPlainText = (html: string): string =>
html
.replace(/<br\s*\/?>(\n)?/gi, "\n")
.replace(/<div><br\s*\/?><\/div>/gi, "")
.replace(/<[^>]*>/g, "")
.replace(/&nbsp;/g, " ")
.trim();
const handleToggleCompleted = () => {
if (viewOnly) return;
setCompleted((prev) => !prev);
updateItem.mutate({
checklistItemPublicId: item.publicId,
completed: !completed,
});
};
const commitTitle = (rawHtml: string) => {
if (viewOnly) return;
const plain = sanitizeHtmlToPlainText(rawHtml);
if (!plain || plain === item.title) {
setTitle(item.title);
return;
}
setTitle(plain);
updateItem.mutate({
checklistItemPublicId: item.publicId,
title: plain,
});
};
const handleDelete = () => {
if (viewOnly) return;
deleteItem.mutate({ checklistItemPublicId: item.publicId });
};
return (
<div className="group relative flex items-start gap-3 rounded-md py-2 pl-4 hover:bg-light-100 dark:hover:bg-dark-100">
<label
className={`relative mt-[2px] inline-flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center`}
>
<input
type="checkbox"
checked={completed}
onChange={(e) => {
if (viewOnly) {
e.preventDefault();
return;
}
handleToggleCompleted();
}}
className={twMerge(
"h-[16px] w-[16px] appearance-none rounded-md border border-light-500 bg-transparent outline-none ring-0 checked:bg-blue-600 focus:shadow-none focus:ring-0 focus:ring-offset-0 focus-visible:outline-none dark:border-dark-500 dark:hover:border-dark-500",
viewOnly ? "cursor-default" : "cursor-pointer",
)}
/>
</label>
<div className="flex-1 pr-7">
<ContentEditable
html={title}
disabled={viewOnly}
onChange={(e) => setTitle(e.target.value)}
// @ts-expect-error - valid event
onBlur={(e: Event) => commitTitle(e.target.innerHTML as string)}
className={twMerge(
"m-0 min-h-[20px] w-full p-0 text-sm leading-[20px] text-light-950 outline-none focus-visible:outline-none dark:text-dark-950",
viewOnly && "cursor-default",
)}
placeholder={t`Add details...`}
onKeyDown={(e) => {
if (viewOnly) return;
if (e.key === "Enter") {
e.preventDefault();
commitTitle(title);
}
if (e.key === "Escape") {
e.preventDefault();
setTitle(item.title);
}
}}
/>
</div>
{!viewOnly && (
<button
type="button"
onClick={handleDelete}
className="absolute right-1 top-1/2 hidden -translate-y-1/2 rounded-md p-1 text-light-900 group-hover:block hover:bg-light-200 dark:text-dark-700 dark:hover:bg-dark-200"
>
<HiXMark size={16} />
</button>
)}
</div>
);
}

View File

@@ -0,0 +1,90 @@
import { t } from "@lingui/core/macro";
import { useEffect, useRef, useState } from "react";
import { twMerge } from "tailwind-merge";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
export default function ChecklistNameInput({
checklistPublicId,
initialName,
cardPublicId,
viewOnly = false,
}: {
checklistPublicId: string;
initialName: string;
cardPublicId: string;
viewOnly?: boolean;
}) {
const utils = api.useUtils();
const { showPopup } = usePopup();
const [name, setName] = useState(initialName);
const inputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
setName(initialName);
}, [initialName, checklistPublicId]);
const update = api.checklist.update.useMutation({
onMutate: async (vars) => {
await utils.card.byId.cancel({ cardPublicId });
const previous = utils.card.byId.getData({ cardPublicId });
utils.card.byId.setData({ cardPublicId }, (old) => {
if (!old) return old as any;
const updated = old.checklists.map((cl) =>
cl.publicId === checklistPublicId ? { ...cl, name: vars.name } : cl,
);
return { ...old, checklists: updated } as typeof old;
});
return { previous };
},
onError: (_err, _vars, ctx) => {
if (ctx?.previous)
utils.card.byId.setData({ cardPublicId }, ctx.previous);
showPopup({
header: t`Unable to update checklist`,
message: t`Please try again later, or contact customer support.`,
icon: "error",
});
},
onSettled: async () => {
await utils.card.byId.invalidate({ cardPublicId });
},
});
const commit = () => {
if (viewOnly) return;
const trimmed = name.trim();
if (!trimmed || trimmed === initialName) return;
update.mutate({ checklistPublicId, name: trimmed });
};
return (
<input
ref={inputRef}
type="text"
value={name}
readOnly={viewOnly}
onChange={(e) => setName(e.target.value)}
onBlur={commit}
onKeyDown={(e) => {
if (viewOnly) return;
if (e.key === "Enter") {
e.preventDefault();
commit();
inputRef.current?.blur();
}
if (e.key === "Escape") {
e.preventDefault();
setName(initialName);
inputRef.current?.blur();
}
}}
title={name}
className={twMerge(
"text-md block w-full truncate border-0 bg-transparent p-0 py-0 font-medium text-light-1000 outline-none focus:ring-0 dark:text-dark-1000",
viewOnly && "cursor-default",
)}
/>
);
}

View File

@@ -0,0 +1,142 @@
import { HiPlus, HiXMark } from "react-icons/hi2";
import CircularProgress from "~/components/CircularProgress";
import { useModal } from "~/providers/modal";
import ChecklistItemRow from "./ChecklistItemRow";
import ChecklistNameInput from "./ChecklistNameInput";
import NewChecklistItemForm from "./NewChecklistItemForm";
interface ChecklistItem {
publicId: string;
title: string;
completed: boolean;
}
interface Checklist {
publicId: string;
name: string;
items: ChecklistItem[];
}
interface ChecklistsProps {
checklists: Checklist[];
cardPublicId: string;
activeChecklistForm?: string | null;
setActiveChecklistForm?: (id: string | null) => void;
viewOnly?: boolean;
}
export default function Checklists({
checklists,
cardPublicId,
activeChecklistForm,
setActiveChecklistForm,
viewOnly = false,
}: ChecklistsProps) {
const { openModal } = useModal();
if (!checklists || checklists.length === 0) return null;
return (
<div className="border-light-300 pb-4 dark:border-dark-300">
<div>
{checklists.map((checklist) => {
const completedItems = checklist.items.filter(
(item) => item.completed,
);
const progress =
checklist.items.length > 0 && completedItems.length > 0
? (completedItems.length / checklist.items.length) * 100
: 2;
return (
<div key={checklist.publicId} className="mb-4">
<div className="mb-2 flex items-center font-medium text-light-1000 dark:text-dark-1000">
<div className="min-w-0 flex-1">
<ChecklistNameInput
checklistPublicId={checklist.publicId}
initialName={checklist.name}
cardPublicId={cardPublicId}
viewOnly={viewOnly}
/>
</div>
{!viewOnly && (
<div className="ml-2 flex flex-shrink-0 items-center gap-2">
<div className="flex items-center gap-1 rounded-full border-[1px] border-light-300 px-2 py-1 dark:border-dark-300">
<CircularProgress
progress={progress}
size="sm"
className="flex-shrink-0"
/>
<span className="text-[11px] text-light-900 dark:text-dark-700">
{completedItems.length}/{checklist.items.length}
</span>
</div>
<div>
<button
className="rounded-md p-1 text-light-900 hover:bg-light-100 dark:text-dark-700 dark:hover:bg-dark-100"
onClick={() =>
openModal("DELETE_CHECKLIST", checklist.publicId)
}
>
<HiXMark size={16} />
</button>
<button
onClick={() =>
setActiveChecklistForm?.(checklist.publicId)
}
className="rounded-md p-1 text-light-900 hover:bg-light-100 dark:text-dark-700 dark:hover:bg-dark-100"
>
<HiPlus size={16} />
</button>
</div>
</div>
)}
{viewOnly && (
<div className="ml-2 flex flex-shrink-0 items-center gap-2">
<div className="flex items-center gap-1 rounded-full border-[1px] border-light-300 px-2 py-1 dark:border-dark-300">
<CircularProgress
progress={progress}
size="sm"
className="flex-shrink-0"
/>
<span className="text-[11px] text-light-900 dark:text-dark-700">
{completedItems.length}/{checklist.items.length}
</span>
</div>
</div>
)}
</div>
<div className="ml-1">
{checklist.items.map((item) => (
<ChecklistItemRow
key={item.publicId}
item={{
publicId: item.publicId,
title: item.title,
completed: item.completed,
}}
cardPublicId={cardPublicId}
viewOnly={viewOnly}
/>
))}
</div>
{activeChecklistForm === checklist.publicId && !viewOnly && (
<div className="ml-1">
<NewChecklistItemForm
checklistPublicId={checklist.publicId}
cardPublicId={cardPublicId}
onCancel={() => setActiveChecklistForm?.(null)}
readOnly={viewOnly}
/>
</div>
)}
</div>
);
})}
</div>
</div>
);
}

View File

@@ -27,6 +27,7 @@ const Comment = ({
isAuthor,
isAdmin,
isEdited = false,
isViewOnly = false,
}: {
publicId: string | undefined;
cardPublicId: string;
@@ -38,6 +39,7 @@ const Comment = ({
isAuthor: boolean;
isAdmin: boolean;
isEdited: boolean;
isViewOnly: boolean;
}) => {
const [isEditing, setIsEditing] = useState(false);
const utils = api.useUtils();
@@ -124,7 +126,7 @@ const Comment = ({
</p>
</div>
{dropdownItems.length > 0 && (
{dropdownItems.length > 0 && !isViewOnly && (
<div className="absolute right-4 top-4">
<Dropdown items={dropdownItems}>
<HiEllipsisHorizontal className="h-5 w-5 text-light-900 dark:text-dark-800" />

View File

@@ -0,0 +1,71 @@
import { t } from "@lingui/core/macro";
import Button from "~/components/Button";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
export function DeleteChecklistConfirmation({
cardPublicId,
checklistPublicId,
}: {
cardPublicId: string;
checklistPublicId: string;
}) {
const { closeModal } = useModal();
const { showPopup } = usePopup();
const utils = api.useUtils();
const deleteChecklist = api.checklist.delete.useMutation({
onMutate: async () => {
await utils.card.byId.cancel({ cardPublicId });
const previous = utils.card.byId.getData({ cardPublicId });
utils.card.byId.setData({ cardPublicId }, (old) => {
if (!old) return old as any;
const updatedChecklists = old.checklists.filter(
(cl) => cl.publicId !== checklistPublicId,
);
return { ...old, checklists: updatedChecklists } as typeof old;
});
return { previous };
},
onError: (_err, _vars, ctx) => {
if (ctx?.previous)
utils.card.byId.setData({ cardPublicId }, ctx.previous);
showPopup({
header: t`Unable to delete checklist`,
message: t`Please try again later, or contact customer support.`,
icon: "error",
});
},
onSettled: async () => {
closeModal();
await utils.card.byId.invalidate({ cardPublicId });
},
});
const handleDelete = () => {
deleteChecklist.mutate({ checklistPublicId });
};
return (
<div className="p-5">
<div className="flex w-full flex-col justify-between pb-4">
<h2 className="text-md pb-4 font-medium text-neutral-900 dark:text-dark-1000">
{t`Are you sure you want to delete this checklist?`}
</h2>
<p className="text-sm font-medium text-light-900 dark:text-dark-900">
{t`This action can't be undone.`}
</p>
</div>
<div className="mt-5 flex justify-end space-x-2 sm:mt-6">
<Button variant="secondary" onClick={() => closeModal()}>
{t`Cancel`}
</Button>
<Button onClick={handleDelete} isLoading={deleteChecklist.isPending}>
{t`Delete`}
</Button>
</div>
</div>
);
}

View File

@@ -1,5 +1,9 @@
import { t } from "@lingui/core/macro";
import { HiEllipsisHorizontal, HiOutlineTrash } from "react-icons/hi2";
import {
HiEllipsisHorizontal,
HiOutlineCheckCircle,
HiOutlineTrash,
} from "react-icons/hi2";
import Dropdown from "~/components/Dropdown";
import { useModal } from "~/providers/modal";
@@ -10,6 +14,13 @@ export default function BoardDropdown() {
return (
<Dropdown
items={[
{
label: t`Add checklist`,
action: () => openModal("ADD_CHECKLIST"),
icon: (
<HiOutlineCheckCircle className="h-[16px] w-[16px] text-dark-900" />
),
},
{
label: t`Delete card`,
action: () => openModal("DELETE_CARD"),

View File

@@ -0,0 +1,140 @@
import { t } from "@lingui/core/macro";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { HiXMark } from "react-icons/hi2";
import { generateUID } from "@kan/shared/utils";
import Button from "~/components/Button";
import Input from "~/components/Input";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
interface NewChecklistFormInput {
name: string;
cardPublicId: string;
}
export function NewChecklistForm({ cardPublicId }: { cardPublicId: string }) {
const { closeModal, setModalState } = useModal();
const { showPopup } = usePopup();
const utils = api.useUtils();
const { register, handleSubmit, reset, watch } =
useForm<NewChecklistFormInput>({
defaultValues: {
name: "Checklist",
cardPublicId,
},
});
const createChecklist = api.checklist.create.useMutation({
onMutate: async (args) => {
await utils.card.byId.cancel({ cardPublicId: args.cardPublicId });
const previous = utils.card.byId.getData({
cardPublicId: args.cardPublicId,
});
utils.card.byId.setData({ cardPublicId: args.cardPublicId }, (old) => {
if (!old) return old as any;
const placeholderChecklist = {
publicId: `PLACEHOLDER_${generateUID()}`,
name: args.name,
index: old.checklists.length,
items: [] as {
publicId: string;
title: string;
completed: boolean;
index: number;
}[],
};
return {
...old,
checklists: [...old.checklists, placeholderChecklist],
} as typeof old;
});
return { previous };
},
onSuccess: (data) => {
setModalState("ADD_CHECKLIST", { createdChecklistId: data.publicId });
},
onError: (_error, vars, ctx) => {
if (ctx?.previous)
utils.card.byId.setData(
{ cardPublicId: vars.cardPublicId },
ctx.previous,
);
showPopup({
header: t`Unable to create checklist`,
message: t`Please try again later, or contact customer support.`,
icon: "error",
});
},
onSettled: async (_data, _error, vars) => {
await utils.card.byId.invalidate({ cardPublicId: vars.cardPublicId });
},
});
useEffect(() => {
const nameElement: HTMLElement | null =
document.querySelector<HTMLElement>("#checklist-name");
if (nameElement) nameElement.focus();
}, []);
const onSubmit = (data: NewChecklistFormInput) => {
closeModal();
reset({
name: "",
});
createChecklist.mutate({
name: data.name,
cardPublicId: data.cardPublicId,
});
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="px-5 pt-5">
<div className="flex w-full items-center justify-between pb-4">
<h2 className="text-sm font-bold text-neutral-900 dark:text-dark-1000">
{t`New checklist`}
</h2>
<button
type="button"
className="rounded p-1 hover:bg-light-200 focus:outline-none dark:hover:bg-dark-300"
onClick={(e) => {
e.preventDefault();
closeModal();
}}
>
<HiXMark size={18} className="text-light-900 dark:text-dark-900" />
</button>
</div>
<Input
id="checklist-name"
placeholder={t`Checklist name`}
{...register("name")}
onKeyDown={async (e) => {
if (e.key === "Enter") {
e.preventDefault();
await handleSubmit(onSubmit)();
}
}}
/>
</div>
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
<div>
<Button
type="submit"
disabled={createChecklist.isPending || !watch("name")}
>
{t`Create checklist`}
</Button>
</div>
</div>
</form>
);
}

View File

@@ -0,0 +1,167 @@
import { t } from "@lingui/core/macro";
import { useEffect, useRef } from "react";
import ContentEditable from "react-contenteditable";
import { useForm } from "react-hook-form";
import { generateUID } from "@kan/shared/utils";
import { usePopup } from "~/providers/popup";
import { api } from "~/utils/api";
interface FormValues {
title: string;
}
interface NewChecklistItemFormProps {
checklistPublicId: string;
cardPublicId: string;
onCancel: () => void;
readOnly?: boolean;
}
const NewChecklistItemForm = ({
checklistPublicId,
cardPublicId,
onCancel,
readOnly = false,
}: NewChecklistItemFormProps) => {
const utils = api.useUtils();
const { showPopup } = usePopup();
const { setValue, watch, reset, getValues } = useForm<FormValues>({
defaultValues: {
title: "",
},
});
const title = watch("title");
const editableRef = useRef<HTMLElement | null>(null);
const keepOpenRef = useRef(false);
const refocusEditable = () => {
const el = editableRef.current;
if (!el) return;
if (readOnly) return;
el.focus();
// hack to ensure the input is focused after creating new checklist item
setTimeout(() => {
el.focus();
}, 100);
};
const addChecklistItemMutation = api.checklist.createItem.useMutation({
onMutate: async (vars) => {
await utils.card.byId.cancel({ cardPublicId });
const previous = utils.card.byId.getData({ cardPublicId });
utils.card.byId.setData({ cardPublicId }, (old) => {
if (!old) return old as any;
const placeholder = {
publicId: `PLACEHOLDER_${generateUID()}`,
title: vars.title,
completed: false,
};
const updatedChecklists = old.checklists.map((cl) =>
cl.publicId === checklistPublicId
? { ...cl, items: [...cl.items, placeholder] }
: cl,
);
return { ...old, checklists: updatedChecklists } as typeof old;
});
if (keepOpenRef.current) {
reset({ title: "" });
if (editableRef.current) editableRef.current.innerHTML = "";
refocusEditable();
} else {
onCancel();
}
return { previous };
},
onError: (_err, _vars, ctx) => {
if (ctx?.previous)
utils.card.byId.setData({ cardPublicId }, ctx.previous);
showPopup({
header: t`Unable to add checklist item`,
message: t`Please try again later, or contact customer support.`,
icon: "error",
});
},
onSettled: async () => {
await utils.card.byId.invalidate({ cardPublicId });
},
});
const sanitizeHtmlToPlainText = (html: string): string => {
return html
.replace(/<br\s*\/?>(\n)?/gi, "\n")
.replace(/<div><br\s*\/?><\/div>/gi, "")
.replace(/<[^>]*>/g, "")
.replace(/&nbsp;/g, " ")
.trim();
};
const submitIfNotEmpty = (keepOpen: boolean) => {
if (readOnly) return;
keepOpenRef.current = keepOpen;
const currentHtml = getValues("title") ?? "";
const plain = sanitizeHtmlToPlainText(currentHtml);
if (!plain) {
onCancel();
return;
}
addChecklistItemMutation.mutate({
checklistPublicId,
title: plain,
});
};
useEffect(() => {
refocusEditable();
}, []);
return (
<form onSubmit={(e) => e.preventDefault()}>
<div className="group relative flex h-9 items-center gap-3 rounded-md pl-4 hover:bg-light-100 dark:hover:bg-dark-100">
<label className="relative inline-flex h-[16px] w-[16px] flex-shrink-0 cursor-default items-center justify-center">
<input
type="checkbox"
disabled
className="peer h-[16px] w-[16px] appearance-none rounded-md border border-light-500 bg-transparent outline-none ring-0 hover:border-light-500 hover:bg-transparent focus:outline-none focus:ring-0 focus-visible:outline-none dark:border-dark-500 dark:hover:border-dark-500"
/>
</label>
<div className="flex-1 pr-7">
<ContentEditable
id={`checklist-item-input-${checklistPublicId}`}
tabIndex={readOnly ? -1 : 0}
placeholder={t`Add an item...`}
html={title}
disabled={readOnly}
onChange={(e) => setValue("title", e.target.value)}
className="m-0 min-h-[20px] w-full p-0 text-sm leading-5 text-light-900 outline-none focus-visible:outline-none dark:text-dark-950"
onBlur={() => submitIfNotEmpty(false)}
onKeyDown={async (e) => {
if (readOnly) return;
if (e.key === "Enter") {
e.preventDefault();
submitIfNotEmpty(true);
}
if (e.key === "Escape") {
e.preventDefault();
onCancel();
}
}}
innerRef={(el) => {
editableRef.current = (el as unknown as HTMLElement) ?? null;
}}
/>
</div>
</div>
</form>
);
};
export default NewChecklistItemForm;

View File

@@ -1,6 +1,7 @@
import Link from "next/link";
import { useRouter } from "next/router";
import { t } from "@lingui/core/macro";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { IoChevronForwardSharp } from "react-icons/io5";
@@ -19,12 +20,15 @@ import { api } from "~/utils/api";
import { formatMemberDisplayName, getAvatarUrl } from "~/utils/helpers";
import { DeleteLabelConfirmation } from "../../components/DeleteLabelConfirmation";
import ActivityList from "./components/ActivityList";
import Checklists from "./components/Checklists";
import { DeleteCardConfirmation } from "./components/DeleteCardConfirmation";
import { DeleteChecklistConfirmation } from "./components/DeleteChecklistConfirmation";
import { DeleteCommentConfirmation } from "./components/DeleteCommentConfirmation";
import Dropdown from "./components/Dropdown";
import LabelSelector from "./components/LabelSelector";
import ListSelector from "./components/ListSelector";
import MemberSelector from "./components/MemberSelector";
import { NewChecklistForm } from "./components/NewChecklistForm";
import NewCommentForm from "./components/NewCommentForm";
interface FormValues {
@@ -132,9 +136,18 @@ export function CardRightPanel() {
export default function CardPage() {
const router = useRouter();
const utils = api.useUtils();
const { modalContentType, entityId } = useModal();
const {
modalContentType,
entityId,
openModal,
getModalState,
clearModalState,
} = useModal();
const { showPopup } = usePopup();
const { workspace } = useWorkspace();
const [activeChecklistForm, setActiveChecklistForm] = useState<string | null>(
null,
);
const cardId = Array.isArray(router.query.cardId)
? router.query.cardId[0]
@@ -181,6 +194,17 @@ export default function CardPage() {
});
};
// Open the new item form after creating a new checklist
useEffect(() => {
if (!card) return;
const state = getModalState("ADD_CHECKLIST");
const createdId: string | undefined = state?.createdChecklistId;
if (createdId) {
setActiveChecklistForm(createdId);
clearModalState("ADD_CHECKLIST");
}
}, [card, getModalState, clearModalState]);
if (!cardId) return <></>;
return (
@@ -188,87 +212,96 @@ export default function CardPage() {
<PageHead
title={t`${card?.title ?? "Card"} | ${board?.name ?? "Board"}`}
/>
<div className="flex h-full flex-1 flex-row">
<div className="m-auto flex h-full w-full max-w-[800px] flex-col overflow-hidden">
<div className="h-full max-h-[calc(100dvh-3rem)] overflow-y-auto p-6 md:max-h-[calc(100dvh-4rem)] md:p-8">
<div className="mb-8 flex w-full items-center justify-between md:mt-6">
{!card && isLoading && (
<div className="flex space-x-2">
<div className="h-[2.3rem] w-[150px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
<div className="h-[2.3rem] w-[300px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
</div>
)}
<div className="flex h-full flex-1 flex-row overflow-hidden">
<div className="scrollbar-thumb-rounded-[4px] scrollbar-track-rounded-[4px] w-full flex-1 overflow-y-auto scrollbar scrollbar-track-light-200 scrollbar-thumb-light-400 hover:scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-300 dark:hover:scrollbar-thumb-dark-300">
<div className="p-auto mx-auto flex h-full w-full max-w-[800px] flex-col">
<div className="p-6 md:p-8">
<div className="mb-8 flex w-full items-center justify-between md:mt-6">
{!card && isLoading && (
<div className="flex space-x-2">
<div className="h-[2.3rem] w-[150px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
<div className="h-[2.3rem] w-[300px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
</div>
)}
{card && (
<>
<Link
className="whitespace-nowrap font-bold leading-[2.3rem] tracking-tight text-light-900 dark:text-dark-900 sm:text-[1.2rem]"
href={`/boards/${board?.publicId}`}
>
{board?.name}
</Link>
<IoChevronForwardSharp
size={18}
className="mx-2 text-light-900 dark:text-dark-900"
/>
<form
onSubmit={handleSubmit(onSubmit)}
className="w-full space-y-6"
>
<div>
<input
type="text"
id="title"
{...register("title")}
onBlur={handleSubmit(onSubmit)}
className="block w-full border-0 bg-transparent p-0 py-0 font-bold tracking-tight text-neutral-900 focus:ring-0 dark:text-dark-1000 sm:text-[1.2rem]"
/>
</div>
</form>
<div className="flex">
<Dropdown />
</div>
</>
)}
{!card && !isLoading && (
<p className="block p-0 py-0 font-bold leading-[2.3rem] tracking-tight text-neutral-900 dark:text-dark-1000 sm:text-[1.2rem]">
{t`Card not found`}
</p>
)}
</div>
{card && (
<>
<Link
className="whitespace-nowrap font-bold leading-[2.3rem] tracking-tight text-light-900 dark:text-dark-900 sm:text-[1.2rem]"
href={`/boards/${board?.publicId}`}
>
{board?.name}
</Link>
<IoChevronForwardSharp
size={18}
className="mx-2 text-light-900 dark:text-dark-900"
<div className="mb-10 flex w-full max-w-2xl flex-col justify-between">
<form
onSubmit={handleSubmit(onSubmit)}
className="w-full space-y-6"
>
<div className="mt-2">
<Editor
content={card.description}
onChange={(e) => setValue("description", e)}
onBlur={() => handleSubmit(onSubmit)()}
workspaceMembers={board?.workspace.members ?? []}
/>
</div>
</form>
</div>
<Checklists
checklists={card.checklists}
cardPublicId={cardId}
activeChecklistForm={activeChecklistForm}
setActiveChecklistForm={setActiveChecklistForm}
/>
<form
onSubmit={handleSubmit(onSubmit)}
className="w-full space-y-6"
>
<div className="border-t-[1px] border-light-300 pt-12 dark:border-dark-300">
<h2 className="text-md pb-4 font-medium text-light-1000 dark:text-dark-1000">
{t`Activity`}
</h2>
<div>
<input
type="text"
id="title"
{...register("title")}
onBlur={handleSubmit(onSubmit)}
className="block w-full border-0 bg-transparent p-0 py-0 font-bold tracking-tight text-neutral-900 focus:ring-0 dark:text-dark-1000 sm:text-[1.2rem]"
<ActivityList
cardPublicId={cardId}
activities={activities ?? []}
isLoading={!card}
isAdmin={workspace.role === "admin"}
/>
</div>
</form>
<div className="flex">
<Dropdown />
<div className="mt-6">
<NewCommentForm cardPublicId={cardId} />
</div>
</div>
</>
)}
{!card && !isLoading && (
<p className="block p-0 py-0 font-bold leading-[2.3rem] tracking-tight text-neutral-900 dark:text-dark-1000 sm:text-[1.2rem]">
{t`Card not found`}
</p>
)}
</div>
{card && (
<>
<div className="mb-10 flex w-full max-w-2xl flex-col justify-between">
<form
onSubmit={handleSubmit(onSubmit)}
className="w-full space-y-6"
>
<div className="mt-2">
<Editor
content={card.description}
onChange={(e) => setValue("description", e)}
onBlur={() => handleSubmit(onSubmit)()}
/>
</div>
</form>
</div>
<div className="border-t-[1px] border-light-300 pt-12 dark:border-dark-300">
<h2 className="text-md pb-4 font-medium text-light-900 dark:text-dark-1000">
{t`Activity`}
</h2>
<div>
<ActivityList
cardPublicId={cardId}
activities={activities ?? []}
isLoading={!card}
isAdmin={workspace.role === "admin"}
/>
</div>
<div className="mt-6">
<NewCommentForm cardPublicId={cardId} />
</div>
</div>
</>
)}
</div>
</div>
@@ -303,6 +336,15 @@ export default function CardPage() {
/>
)}
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
{modalContentType === "ADD_CHECKLIST" && (
<NewChecklistForm cardPublicId={cardId} />
)}
{modalContentType === "DELETE_CHECKLIST" && (
<DeleteChecklistConfirmation
cardPublicId={cardId}
checklistPublicId={entityId}
/>
)}
</Modal>
</div>
</>

View File

@@ -1,26 +1,43 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/core/macro";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { HiXMark } from "react-icons/hi2";
import { z } from "zod";
import type { InviteMemberInput } from "@kan/api/types";
import Button from "~/components/Button";
import Input from "~/components/Input";
import Toggle from "~/components/Toggle";
import { useModal } from "~/providers/modal";
import { usePopup } from "~/providers/popup";
import { useWorkspace } from "~/providers/workspace";
import { api } from "~/utils/api";
export function InviteMemberForm() {
const utils = api.useUtils();
const [isCreateAnotherEnabled, setIsCreateAnotherEnabled] = useState(false);
const { closeModal } = useModal();
const { workspace } = useWorkspace();
const { showPopup } = usePopup();
const { register, handleSubmit } = useForm<InviteMemberInput>({
const InviteMemberSchema = z.object({
email: z.string().email({ message: t`Invalid email address` }),
workspacePublicId: z.string(),
});
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm<InviteMemberInput>({
defaultValues: {
email: "",
workspacePublicId: workspace.publicId || "",
},
resolver: zodResolver(InviteMemberSchema),
});
const refetchBoards = () => utils.board.all.refetch();
@@ -31,6 +48,24 @@ export function InviteMemberForm() {
await utils.workspace.byId.refetch();
await refetchBoards();
},
onError: (error) => {
reset();
if (!isCreateAnotherEnabled) closeModal();
if (error.data?.code === "CONFLICT") {
showPopup({
header: t`Error inviting member`,
message: t`User is already a member of this workspace`,
icon: "error",
});
} else {
showPopup({
header: t`Error inviting member`,
message: t`Please try again later, or contact customer support.`,
icon: "error",
});
}
},
});
const onSubmit = (data: InviteMemberInput) => {
@@ -69,10 +104,16 @@ export function InviteMemberForm() {
await handleSubmit(onSubmit)();
}
}}
errorMessage={errors.email?.message}
/>
</div>
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
<Toggle
label={t`Invite another`}
isChecked={isCreateAnotherEnabled}
onChange={() => setIsCreateAnotherEnabled(!isCreateAnotherEnabled)}
/>
<div>
<Button
type="submit"

View File

@@ -97,7 +97,7 @@ export default function MembersPage() {
</td>
<td
className={twMerge(
"w-auto min-w-[120px] sm:w-[35%] sm:min-w-[150px]",
"w-auto min-w-[120px] overflow-visible sm:w-[35%] sm:min-w-[150px]",
isLastRow && "rounded-br-lg",
)}
>
@@ -121,7 +121,7 @@ export default function MembersPage() {
</div>
<div
className={twMerge(
"relative",
"relative z-50",
(workspace.role !== "admin" || showSkeleton) && "hidden",
)}
>
@@ -172,10 +172,10 @@ export default function MembersPage() {
</div>
<div className="mt-8 flow-root">
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full px-4 py-2 align-middle sm:px-6 lg:px-8">
<div className="-mx-4 -my-2 sm:-mx-6 lg:-mx-8">
<div className="inline-block min-w-full overflow-x-auto px-4 py-2 pb-16 align-middle sm:px-6 lg:px-8">
<div className="h-full shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
<table className="min-w-full divide-y divide-light-600 dark:divide-dark-600">
<table className="min-w-full divide-y divide-light-600 overflow-visible dark:divide-dark-600">
<thead className="rounded-t-lg bg-light-300 dark:bg-dark-200">
<tr>
<th
@@ -192,7 +192,7 @@ export default function MembersPage() {
</th>
</tr>
</thead>
<tbody className="divide-y divide-light-600 bg-light-50 dark:divide-dark-600 dark:bg-dark-100">
<tbody className="divide-y divide-light-600 overflow-visible bg-light-50 dark:divide-dark-600 dark:bg-dark-100">
{!isLoading &&
data?.members.map((member, index) => (
<TableRow

View File

@@ -1,13 +1,15 @@
import { useRouter } from "next/router";
import { t } from "@lingui/core/macro";
import { useEffect, useRef, useState } from "react";
import { HiXMark } from "react-icons/hi2";
import { authClient } from "@kan/auth/client";
import Badge from "~/components/Badge";
import Editor from "~/components/Editor";
import LabelIcon from "~/components/LabelIcon";
import { useModal } from "~/providers/modal";
import { api } from "~/utils/api";
import ActivityList from "~/views/card/components/ActivityList";
import Checklists from "~/views/card/components/Checklists";
export function CardModal({
cardPublicId,
@@ -20,7 +22,9 @@ export function CardModal({
}) {
const router = useRouter();
const { closeModal, isOpen } = useModal();
const { data: session } = authClient.useSession();
const [showFade, setShowFade] = useState(false);
const [showTopFade, setShowTopFade] = useState(false);
const scrollRef = useRef<HTMLDivElement>(null);
const { data, isLoading } = api.card.byId.useQuery(
{
@@ -33,85 +37,126 @@ export function CardModal({
const labels = data?.labels ?? [];
const handleScroll = () => {
if (!scrollRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 5;
const isAtTop = scrollTop <= 5;
setShowFade(!isAtBottom);
setShowTopFade(!isAtTop);
};
useEffect(() => {
const scrollElement = scrollRef.current;
if (!scrollElement) return;
handleScroll();
scrollElement.addEventListener("scroll", handleScroll);
return () => scrollElement.removeEventListener("scroll", handleScroll);
}, [data]);
return (
<div className="flex h-full flex-1 flex-row">
<div className="flex h-full w-full flex-col overflow-hidden">
<div className="h-full max-h-[calc(100vh-4rem)] overflow-y-auto p-8">
<div className="flex w-full items-center justify-between">
<button
className="absolute right-[2rem] top-[2rem] rounded p-1 hover:bg-light-300 focus:outline-none dark:hover:bg-dark-300"
onClick={async (e) => {
e.preventDefault();
closeModal();
try {
await router.replace(
`/${workspaceSlug}/${boardSlug}`,
undefined,
{
shallow: true,
},
);
} catch (error) {
console.error(error);
}
}}
>
<HiXMark
size={18}
className="dark:text-dark-9000 text-light-900"
/>
</button>
{isLoading ? (
<div className="flex space-x-2">
<div className="h-[2.3rem] w-[300px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
</div>
) : (
<>
<h1 className="font-medium leading-[2.3rem] tracking-tight text-neutral-900 dark:text-dark-1000 sm:text-[1.2rem]">
{data?.title}
</h1>
</>
)}
</div>
<div className="h-full p-8">
<div className="mb-6">
{labels.map((label) => (
<div
key={label.publicId}
className="my-1 mr-1 inline-flex w-fit items-center gap-x-1.5 rounded-full px-2 py-1 text-[12px] font-medium text-light-800 ring-1 ring-inset ring-light-600 dark:text-dark-1000 dark:ring-dark-800"
<div className="flex w-full items-center justify-between">
<button
className="absolute right-[2rem] top-[2rem] rounded p-1 hover:bg-light-300 focus:outline-none dark:hover:bg-dark-300"
onClick={async (e) => {
e.preventDefault();
closeModal();
try {
await router.replace(
`/${workspaceSlug}/${boardSlug}`,
undefined,
{
shallow: true,
},
);
} catch (error) {
console.error(error);
}
}}
>
<svg
fill={label.colourCode ?? "#3730a3"}
className="h-2 w-2"
viewBox="0 0 6 6"
aria-hidden="true"
>
<circle cx={3} cy={3} r={3} />
</svg>
<div>{label.name}</div>
</div>
))}
</div>
{data?.description && (
<div className="mb-10 flex w-full max-w-2xl justify-between">
<div className="mt-2">
<Editor content={data.description} readOnly />
</div>
</div>
)}
<div className="border-t-[1px] border-light-600 pb-4 pt-12 dark:border-dark-400">
<h2 className="text-md pb-4 font-medium text-light-900 dark:text-dark-1000">
{t`Activity`}
</h2>
<div>
{cardPublicId && (
<ActivityList
cardPublicId={cardPublicId}
activities={data?.activities ?? []}
isLoading={isLoading}
<HiXMark
size={18}
className="dark:text-dark-9000 text-light-900"
/>
</button>
{isLoading ? (
<div className="flex space-x-2">
<div className="h-[2.3rem] w-[300px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
</div>
) : (
<>
<h1 className="pr-8 font-bold leading-[2.3rem] tracking-tight text-neutral-900 dark:text-dark-1000 sm:text-[1.2rem]">
{data?.title}
</h1>
</>
)}
</div>
{labels.length > 0 && (
<div className="mt-2">
{labels.map((label) => (
<Badge
key={label.publicId}
value={label.name}
iconLeft={<LabelIcon colourCode={label.colourCode} />}
/>
))}
</div>
)}
</div>
<div className="relative">
<div
ref={scrollRef}
className="h-full max-h-[425px] overflow-y-auto"
>
{data?.description && (
<div className="mb-10 flex w-full max-w-2xl justify-between">
<div className="mt-2">
<Editor
content={data.description}
readOnly
workspaceMembers={data?.list.board.workspace.members ?? []}
/>
</div>
</div>
)}
{data?.checklists && data.checklists.length > 0 && (
<Checklists
checklists={data.checklists}
cardPublicId={cardPublicId ?? ""}
viewOnly
/>
)}
<div className="border-t-[1px] border-light-600 pb-4 pt-12 dark:border-dark-400">
<h2 className="text-md pb-4 font-medium text-light-900 dark:text-dark-1000">
{t`Activity`}
</h2>
<div>
{cardPublicId && (
<ActivityList
cardPublicId={cardPublicId}
activities={data?.activities ?? []}
isLoading={isLoading}
isViewOnly={true}
/>
)}
</div>
</div>
</div>
{showTopFade && (
<div className="pointer-events-none absolute left-0 right-0 top-0 h-6 bg-gradient-to-b from-white/80 to-transparent dark:from-dark-100/80" />
)}
{showFade && (
<div className="pointer-events-none absolute bottom-0 left-0 right-0 h-6 bg-gradient-to-t from-white/80 to-transparent dark:from-dark-100/80" />
)}
</div>
</div>
</div>

View File

@@ -2,9 +2,11 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { t } from "@lingui/core/macro";
import { keepPreviousData } from "@tanstack/react-query";
import { env } from "next-runtime-env";
import { useEffect, useState } from "react";
import { HiLink } from "react-icons/hi2";
import { HiLink, HiOutlineLockClosed } from "react-icons/hi2";
import Button from "~/components/Button";
import Modal from "~/components/modal";
import { PageHead } from "~/components/PageHead";
import PatternedBackground from "~/components/PatternedBackground";
@@ -18,6 +20,10 @@ import Card from "~/views/board/components/Card";
import Filters from "~/views/board/components/Filters";
import { CardModal } from "./CardModal";
const IS_CLOUD = env("NEXT_PUBLIC_KAN_ENV") === "cloud";
const HIDE_POWERED_BY =
env("NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY") === "true";
export default function PublicBoardView() {
const router = useRouter();
const { showPopup } = usePopup();
@@ -28,9 +34,14 @@ export default function PublicBoardView() {
? router.query.boardSlug[0]
: router.query.boardSlug;
const workspaceSlug = Array.isArray(router.query.workspaceSlug)
? router.query.workspaceSlug[0]
: router.query.workspaceSlug;
const { data, isLoading } = api.board.bySlug.useQuery(
{
boardSlug: boardSlug ?? "",
workspaceSlug: workspaceSlug ?? "",
members: formatToArray(router.query.members),
labels: formatToArray(router.query.labels),
},
@@ -106,7 +117,13 @@ export default function PublicBoardView() {
</h1>
)}
{data && (
<div className="flex items-center space-x-2">
<div className="z-10 flex items-center space-x-2">
<div className="inline-flex cursor-default items-center justify-center whitespace-nowrap rounded-md border-[1px] border-light-300 bg-light-50 px-3 py-2 text-sm font-semibold text-light-950 shadow-sm dark:border-dark-300 dark:bg-dark-50 dark:text-dark-950">
<span className="mr-2">
<HiOutlineLockClosed />
</span>
{t`View only`}
</div>
<Filters
labels={data.labels ?? []}
members={[]}
@@ -116,17 +133,30 @@ export default function PublicBoardView() {
)}
</div>
<div className="scrollbar-w-none scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-h-[8px] relative flex-1 overflow-y-hidden overflow-x-scroll overscroll-contain scrollbar scrollbar-track-light-200 scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-300">
<div className="scrollbar-w-none scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-h-[8px] relative h-full flex-1 overflow-y-hidden overflow-x-scroll overscroll-contain scrollbar scrollbar-track-light-200 scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-300">
{isLoading ? (
<div className="ml-[2rem] flex">
<div className="0 mr-5 h-[500px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
<div className="0 mr-5 h-[275px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
<div className="0 mr-5 h-[375px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
</div>
) : !data ? (
<div className="z-10 flex h-full w-full flex-col items-center justify-center space-y-8 pb-[150px]">
<div className="flex flex-col items-center">
<HiOutlineLockClosed className="h-10 w-10 text-light-800 dark:text-dark-800" />
<p className="mb-2 mt-4 text-[14px] font-bold text-light-1000 dark:text-dark-950">
{t`Board not found`}
</p>
<p className="text-[14px] text-light-900 dark:text-dark-900">
{t`This board is private or does not exist`}
</p>
</div>
<Button href={`/${workspaceSlug}`}>{t`View workspace`}</Button>
</div>
) : (
<div className="flex">
<div className="min-w-[2rem]" />
{data?.lists.map((list) => (
{data.lists.map((list) => (
<div
key={list.publicId}
className="dark-text-dark-1000 mr-5 h-fit min-w-[18rem] max-w-[18rem] rounded-md border border-light-400 bg-light-300 py-2 pl-2 pr-1 text-neutral-900 dark:border-dark-300 dark:bg-dark-100"
@@ -150,6 +180,7 @@ export default function PublicBoardView() {
<Card
title={card.title}
labels={card.labels}
checklists={card.checklists ?? []}
members={[]}
/>
</Link>
@@ -168,12 +199,26 @@ export default function PublicBoardView() {
<CopyBoardLink />
</div>
<Link
className="text-lg font-bold tracking-tight text-neutral-900 dark:text-dark-1000"
href="/"
>
kan.bn
</Link>
{IS_CLOUD && (
<Link
className="text-lg font-bold tracking-tight text-neutral-900 dark:text-dark-1000"
href="/"
>
kan.bn
</Link>
)}
{!IS_CLOUD && !HIDE_POWERED_BY && (
<a
href="https://kan.bn"
target="_blank"
rel="noreferrer noopener"
className="absolute right-[1rem] inline-flex items-center gap-[0.175rem] rounded-full border border-light-300 bg-light-50 px-3 py-1 text-[11px] font-medium text-light-950 shadow-sm transition-colors hover:bg-light-100 dark:border-dark-300 dark:bg-dark-50 dark:text-dark-900 dark:hover:bg-dark-100"
>
<span>{`Powered by`}</span>
<span className="font-semibold">kan.bn</span>
</a>
)}
</div>
</div>
<Popup />

View File

@@ -23,13 +23,15 @@ services:
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
# SMTP
# Email
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_USER=${SMTP_USER}
- SMTP_PASSWORD=${SMTP_PASSWORD}
- SMTP_SECURE=${SMTP_SECURE}
- EMAIL_FROM=${EMAIL_FROM}
- PLUNK_API_KEY=${PLUNK_API_KEY}
- PLUNK_API_URL=${PLUNK_API_URL}
# S3 storage
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID}

View File

@@ -35,6 +35,9 @@ services:
- NEXT_PUBLIC_AVATAR_BUCKET_NAME=${NEXT_PUBLIC_AVATAR_BUCKET_NAME}
- NEXT_PUBLIC_STORAGE_DOMAIN=${NEXT_PUBLIC_STORAGE_DOMAIN}
# White label
- NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY=${NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY}
# Auth config (optional)
- NEXT_PUBLIC_ALLOW_CREDENTIALS=${NEXT_PUBLIC_ALLOW_CREDENTIALS}
- NEXT_PUBLIC_DISABLE_SIGN_UP=${NEXT_PUBLIC_DISABLE_SIGN_UP}

View File

@@ -1,5 +1,6 @@
import { boardRouter } from "./routers/board";
import { cardRouter } from "./routers/card";
import { checklistRouter } from "./routers/checklist";
import { feedbackRouter } from "./routers/feedback";
import { importRouter } from "./routers/import";
import { integrationRouter } from "./routers/integration";
@@ -13,6 +14,7 @@ import { createTRPCRouter } from "./trpc";
export const appRouter = createTRPCRouter({
board: boardRouter,
card: cardRouter,
checklist: checklistRouter,
feedback: feedbackRouter,
label: labelRouter,
list: listRouter,

View File

@@ -111,15 +111,21 @@ export const boardRouter = createTRPCRouter({
.meta({
openapi: {
method: "GET",
path: "/board/{boardSlug}",
path: "/workspaces/{workspaceSlug}/boards/{boardSlug}",
summary: "Get board by slug",
description: "Retrieves a board by its slug",
description:
"Retrieves a board by its slug within a specific workspace",
tags: ["Boards"],
protect: false,
},
})
.input(
z.object({
workspaceSlug: z
.string()
.min(3)
.max(24)
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/),
boardSlug: z
.string()
.min(3)
@@ -131,10 +137,26 @@ export const boardRouter = createTRPCRouter({
)
.output(z.custom<Awaited<ReturnType<typeof boardRepo.getBySlug>>>())
.query(async ({ ctx, input }) => {
const result = await boardRepo.getBySlug(ctx.db, input.boardSlug, {
members: input.members ?? [],
labels: input.labels ?? [],
});
const workspace = await workspaceRepo.getBySlugWithBoards(
ctx.db,
input.workspaceSlug,
);
if (!workspace)
throw new TRPCError({
message: `Workspace with slug ${input.workspaceSlug} not found`,
code: "NOT_FOUND",
});
const result = await boardRepo.getBySlug(
ctx.db,
input.boardSlug,
workspace.id,
{
members: input.members ?? [],
labels: input.labels ?? [],
},
);
return result;
}),
@@ -276,6 +298,21 @@ export const boardRouter = createTRPCRouter({
await assertUserInWorkspace(ctx.db, userId, board.workspaceId);
if (input.slug) {
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
ctx.db,
input.slug,
board.workspaceId,
);
if (!isBoardSlugAvailable) {
throw new TRPCError({
message: `Board slug ${input.slug} is not available`,
code: "BAD_REQUEST",
});
}
}
const result = await boardRepo.update(ctx.db, {
name: input.name,
slug: input.slug,
@@ -407,11 +444,23 @@ export const boardRouter = createTRPCRouter({
}),
)
.query(async ({ ctx, input }) => {
const board = await boardRepo.getWorkspaceAndBoardIdByBoardPublicId(
ctx.db,
input.boardPublicId,
);
if (!board)
throw new TRPCError({
message: `Board with public ID ${input.boardPublicId} not found`,
code: "NOT_FOUND",
});
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
ctx.db,
input.boardSlug,
input.boardPublicId,
board.workspaceId,
);
return {
isReserved: !isBoardSlugAvailable,
};

View File

@@ -0,0 +1,410 @@
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import * as cardRepo from "@kan/db/repository/card.repo";
import * as cardActivityRepo from "@kan/db/repository/cardActivity.repo";
import * as checklistRepo from "@kan/db/repository/checklist.repo";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import { assertUserInWorkspace } from "../utils/auth";
const checklistSchema = z.object({
publicId: z.string().length(12),
name: z.string().min(1).max(255),
});
const checklistItemSchema = z.object({
publicId: z.string().length(12),
title: z.string().min(1).max(500),
completed: z.boolean(),
});
export const checklistRouter = createTRPCRouter({
create: protectedProcedure
.meta({
openapi: {
summary: "Add a checklist to a card",
method: "POST",
path: "/cards/{cardPublicId}/checklists",
description: "Adds a checklist to a card",
tags: ["Cards"],
protect: true,
},
})
.input(
z.object({
cardPublicId: z.string().length(12),
name: z.string().min(1).max(255),
}),
)
.output(checklistSchema)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId)
throw new TRPCError({
message: `User not authenticated`,
code: "UNAUTHORIZED",
});
const card = await cardRepo.getWorkspaceAndCardIdByCardPublicId(
ctx.db,
input.cardPublicId,
);
if (!card)
throw new TRPCError({
message: `Card with public ID ${input.cardPublicId} not found`,
code: "NOT_FOUND",
});
await assertUserInWorkspace(ctx.db, userId, card.workspaceId);
const newChecklist = await checklistRepo.create(ctx.db, {
name: input.name,
createdBy: userId,
cardId: card.id,
});
if (!newChecklist?.id)
throw new TRPCError({
message: `Failed to create checklist`,
code: "INTERNAL_SERVER_ERROR",
});
await cardActivityRepo.create(ctx.db, {
type: "card.updated.checklist.added",
cardId: card.id,
toTitle: newChecklist.name,
createdBy: userId,
});
return newChecklist;
}),
update: protectedProcedure
.input(
z.object({
checklistPublicId: z.string().length(12),
name: z.string().min(1).max(255),
}),
)
.output(z.object({ publicId: z.string().length(12), name: z.string() }))
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId)
throw new TRPCError({
message: `User not authenticated`,
code: "UNAUTHORIZED",
});
const checklist = await checklistRepo.getChecklistByPublicId(
ctx.db,
input.checklistPublicId,
);
if (!checklist)
throw new TRPCError({
message: `Checklist with public ID ${input.checklistPublicId} not found`,
code: "NOT_FOUND",
});
await assertUserInWorkspace(
ctx.db,
userId,
checklist.card.list.board.workspace.id,
);
const previousName = checklist.name;
const updated = await checklistRepo.updateChecklistById(ctx.db, {
id: checklist.id,
name: input.name,
});
if (!updated)
throw new TRPCError({
message: `Failed to update checklist`,
code: "INTERNAL_SERVER_ERROR",
});
await cardActivityRepo.create(ctx.db, {
type: "card.updated.checklist.renamed",
cardId: checklist.cardId,
fromTitle: previousName,
toTitle: updated.name,
createdBy: userId,
});
return updated;
}),
delete: protectedProcedure
.meta({
openapi: {
summary: "Delete a checklist",
method: "DELETE",
path: "/checklists/{checklistPublicId}",
description: "Deletes a checklist by its public ID",
tags: ["Cards"],
protect: true,
},
})
.input(z.object({ checklistPublicId: z.string().length(12) }))
.output(z.object({ success: z.boolean() }))
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId)
throw new TRPCError({
message: `User not authenticated`,
code: "UNAUTHORIZED",
});
const checklist = await checklistRepo.getChecklistByPublicId(
ctx.db,
input.checklistPublicId,
);
if (!checklist)
throw new TRPCError({
message: `Checklist with public ID ${input.checklistPublicId} not found`,
code: "NOT_FOUND",
});
await assertUserInWorkspace(
ctx.db,
userId,
checklist.card.list.board.workspace.id,
);
await checklistRepo.softDeleteAllItemsByChecklistId(ctx.db, {
checklistId: checklist.id,
deletedAt: new Date(),
deletedBy: userId,
});
const deleted = await checklistRepo.softDeleteById(ctx.db, {
id: checklist.id,
deletedAt: new Date(),
deletedBy: userId,
});
if (!deleted)
throw new TRPCError({
message: `Failed to delete checklist`,
code: "INTERNAL_SERVER_ERROR",
});
await cardActivityRepo.create(ctx.db, {
type: "card.updated.checklist.deleted",
cardId: checklist.cardId,
fromTitle: checklist.name,
createdBy: userId,
});
return { success: true };
}),
createItem: protectedProcedure
.meta({
openapi: {
summary: "Add an item to a checklist",
method: "POST",
path: "/checklists/{checklistPublicId}/items",
description: "Adds an item to a checklist",
tags: ["Cards"],
protect: true,
},
})
.input(
z.object({
checklistPublicId: z.string().length(12),
title: z.string().min(1).max(500),
}),
)
.output(checklistItemSchema)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId)
throw new TRPCError({
message: `User not authenticated`,
code: "UNAUTHORIZED",
});
const checklist = await checklistRepo.getChecklistByPublicId(
ctx.db,
input.checklistPublicId,
);
if (!checklist)
throw new TRPCError({
message: `Checklist with public ID ${input.checklistPublicId} not found`,
code: "NOT_FOUND",
});
await assertUserInWorkspace(
ctx.db,
userId,
checklist.card.list.board.workspace.id,
);
const newChecklistItem = await checklistRepo.createItem(ctx.db, {
title: input.title,
createdBy: userId,
checklistId: checklist.id,
});
if (!newChecklistItem?.id)
throw new TRPCError({
message: `Failed to create checklist item`,
code: "INTERNAL_SERVER_ERROR",
});
await cardActivityRepo.create(ctx.db, {
type: "card.updated.checklist.item.added",
cardId: checklist.cardId,
toTitle: newChecklistItem.title,
createdBy: userId,
});
return newChecklistItem;
}),
updateItem: protectedProcedure
.meta({
openapi: {
summary: "Update a checklist item",
method: "PUT",
path: "/checklists/items/{checklistItemPublicId}",
description: "Updates a checklist item (title/completed)",
tags: ["Cards"],
protect: true,
},
})
.input(
z.object({
checklistItemPublicId: z.string().length(12),
title: z.string().min(1).max(500).optional(),
completed: z.boolean().optional(),
}),
)
.output(checklistItemSchema)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId)
throw new TRPCError({
message: `User not authenticated`,
code: "UNAUTHORIZED",
});
const item = await checklistRepo.getChecklistItemByPublicIdWithChecklist(
ctx.db,
input.checklistItemPublicId,
);
if (!item)
throw new TRPCError({
message: `Checklist item with public ID ${input.checklistItemPublicId} not found`,
code: "NOT_FOUND",
});
await assertUserInWorkspace(
ctx.db,
userId,
item.checklist.card.list.board.workspace.id,
);
const previousTitle = item.title;
const updated = await checklistRepo.updateItemById(ctx.db, {
id: item.id,
title: input.title,
completed: input.completed,
});
if (!updated)
throw new TRPCError({
message: `Failed to update checklist item`,
code: "INTERNAL_SERVER_ERROR",
});
// Log completion toggle
if (input.completed !== undefined) {
await cardActivityRepo.create(ctx.db, {
type: input.completed
? "card.updated.checklist.item.completed"
: "card.updated.checklist.item.uncompleted",
cardId: item.checklist.cardId,
toTitle: updated.title,
createdBy: userId,
});
}
// Log title change
if (input.title !== undefined && input.title !== previousTitle) {
await cardActivityRepo.create(ctx.db, {
type: "card.updated.checklist.item.updated",
cardId: item.checklist.cardId,
fromTitle: previousTitle,
toTitle: updated.title,
createdBy: userId,
});
}
return updated;
}),
deleteItem: protectedProcedure
.meta({
openapi: {
summary: "Delete a checklist item",
method: "DELETE",
path: "/checklists/items/{checklistItemPublicId}",
description: "Deletes a checklist item",
tags: ["Cards"],
protect: true,
},
})
.input(z.object({ checklistItemPublicId: z.string().length(12) }))
.output(z.object({ success: z.boolean() }))
.mutation(async ({ ctx, input }) => {
const userId = ctx.user?.id;
if (!userId)
throw new TRPCError({
message: `User not authenticated`,
code: "UNAUTHORIZED",
});
const item = await checklistRepo.getChecklistItemByPublicIdWithChecklist(
ctx.db,
input.checklistItemPublicId,
);
if (!item)
throw new TRPCError({
message: `Checklist item with public ID ${input.checklistItemPublicId} not found`,
code: "NOT_FOUND",
});
await assertUserInWorkspace(
ctx.db,
userId,
item.checklist.card.list.board.workspace.id,
);
const deleted = await checklistRepo.softDeleteItemById(ctx.db, {
id: item.id,
deletedAt: new Date(),
deletedBy: userId,
});
if (!deleted)
throw new TRPCError({
message: `Failed to delete item`,
code: "INTERNAL_SERVER_ERROR",
});
await cardActivityRepo.create(ctx.db, {
type: "card.updated.checklist.item.deleted",
cardId: item.checklist.cardId,
fromTitle: item.title,
createdBy: userId,
});
return { success: true };
}),
});

View File

@@ -56,7 +56,7 @@ export const memberRouter = createTRPCRouter({
if (isInvitedEmailAlreadyMember) {
throw new TRPCError({
message: `User with email ${input.email} is already a member of this workspace`,
code: "BAD_REQUEST",
code: "CONFLICT",
});
}
@@ -88,6 +88,12 @@ export const memberRouter = createTRPCRouter({
callbackURL: `/boards?type=invite&memberPublicId=${invite.publicId}`,
});
await memberRepo.softDelete(ctx.db, {
memberId: invite.id,
deletedAt: new Date(),
deletedBy: userId,
});
throw new TRPCError({
message: `Failed to send magic link invitation to user with email ${input.email}.`,
code: "INTERNAL_SERVER_ERROR",

View File

@@ -11,7 +11,7 @@ import type { dbClient } from "@kan/db/client";
import * as memberRepo from "@kan/db/repository/member.repo";
import * as userRepo from "@kan/db/repository/user.repo";
import * as schema from "@kan/db/schema";
import { sendEmail } from "@kan/email";
import { cloudMailerClient, sendEmail } from "@kan/email";
import { createStripeClient } from "@kan/stripe";
export const configuredProviders = socialProviderList.reduce<
@@ -183,6 +183,17 @@ export const initAuth = (db: dbClient) => {
return Promise.resolve(true);
},
async after(user) {
if (cloudMailerClient) {
await cloudMailerClient.events.track({
event: "user-signup",
email: user.email,
data: {
name: user.name,
userId: user.id,
},
});
}
if (
user.image &&
!user.image.includes(process.env.NEXT_PUBLIC_STORAGE_DOMAIN!)

View File

@@ -1,4 +1,4 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";--> statement-breakpoint
CREATE TYPE "public"."board_visibility" AS ENUM('private', 'public');--> statement-breakpoint
CREATE TYPE "public"."card_activity_type" AS ENUM('card.created', 'card.updated.title', 'card.updated.description', 'card.updated.index', 'card.updated.list', 'card.updated.label.added', 'card.updated.label.removed', 'card.updated.member.added', 'card.updated.member.removed', 'card.updated.comment.added', 'card.updated.comment.updated', 'card.updated.comment.deleted', 'card.archived');--> statement-breakpoint

View File

@@ -0,0 +1,84 @@
CREATE TABLE IF NOT EXISTS "card_checklist_item" (
"id" bigserial PRIMARY KEY NOT NULL,
"publicId" varchar(12) NOT NULL,
"title" varchar(500) NOT NULL,
"completed" boolean DEFAULT false NOT NULL,
"index" integer NOT NULL,
"checklistId" bigint NOT NULL,
"createdBy" uuid,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp,
"deletedAt" timestamp,
"deletedBy" uuid,
CONSTRAINT "card_checklist_item_publicId_unique" UNIQUE("publicId")
);
--> statement-breakpoint
ALTER TABLE "card_checklist_item" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "card_checklist" (
"id" bigserial PRIMARY KEY NOT NULL,
"publicId" varchar(12) NOT NULL,
"name" varchar(255) NOT NULL,
"index" integer NOT NULL,
"cardId" bigint NOT NULL,
"createdBy" uuid,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp,
"deletedAt" timestamp,
"deletedBy" uuid,
CONSTRAINT "card_checklist_publicId_unique" UNIQUE("publicId")
);
--> statement-breakpoint
ALTER TABLE "card_checklist" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "integration" (
"provider" varchar(255) NOT NULL,
"userId" uuid NOT NULL,
"accessToken" varchar(255) NOT NULL,
"refreshToken" varchar(255),
"expiresAt" timestamp NOT NULL,
"createdAt" timestamp NOT NULL,
"updatedAt" timestamp,
CONSTRAINT "integration_pkey" PRIMARY KEY("userId","provider")
);
--> statement-breakpoint
ALTER TABLE "integration" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "card_checklist_item" ADD CONSTRAINT "card_checklist_item_checklistId_card_checklist_id_fk" FOREIGN KEY ("checklistId") REFERENCES "public"."card_checklist"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "card_checklist_item" ADD CONSTRAINT "card_checklist_item_createdBy_user_id_fk" FOREIGN KEY ("createdBy") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "card_checklist_item" ADD CONSTRAINT "card_checklist_item_deletedBy_user_id_fk" FOREIGN KEY ("deletedBy") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "card_checklist" ADD CONSTRAINT "card_checklist_cardId_card_id_fk" FOREIGN KEY ("cardId") REFERENCES "public"."card"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "card_checklist" ADD CONSTRAINT "card_checklist_createdBy_user_id_fk" FOREIGN KEY ("createdBy") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "card_checklist" ADD CONSTRAINT "card_checklist_deletedBy_user_id_fk" FOREIGN KEY ("deletedBy") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "integration" ADD CONSTRAINT "integration_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@@ -0,0 +1,8 @@
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.added' BEFORE 'card.archived';--> statement-breakpoint
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.renamed' BEFORE 'card.archived';--> statement-breakpoint
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.deleted' BEFORE 'card.archived';--> statement-breakpoint
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.item.added' BEFORE 'card.archived';--> statement-breakpoint
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.item.updated' BEFORE 'card.archived';--> statement-breakpoint
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.item.completed' BEFORE 'card.archived';--> statement-breakpoint
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.item.uncompleted' BEFORE 'card.archived';--> statement-breakpoint
ALTER TYPE "public"."card_activity_type" ADD VALUE 'card.updated.checklist.item.deleted' BEFORE 'card.archived';

View File

@@ -1,6 +1,6 @@
{
"id": "91f9a2fa-31e2-4f3a-bdb9-852292fd7501",
"prevId": "058e8521-2814-46f0-a33f-1bbd86f450ce",
"prevId": "d170c8e0-bf75-4c71-abd9-0ecbeeb14003",
"version": "7",
"dialect": "postgresql",
"tables": {
@@ -92,13 +92,9 @@
"account_userId_user_id_fk": {
"name": "account_userId_user_id_fk",
"tableFrom": "account",
"columnsFrom": [
"userId"
],
"columnsFrom": ["userId"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@@ -245,13 +241,9 @@
"apiKey_userId_user_id_fk": {
"name": "apiKey_userId_user_id_fk",
"tableFrom": "apiKey",
"columnsFrom": [
"userId"
],
"columnsFrom": ["userId"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@@ -320,13 +312,9 @@
"session_userId_user_id_fk": {
"name": "session_userId_user_id_fk",
"tableFrom": "session",
"columnsFrom": [
"userId"
],
"columnsFrom": ["userId"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@@ -335,9 +323,7 @@
"uniqueConstraints": {
"session_token_unique": {
"name": "session_token_unique",
"columns": [
"token"
],
"columns": ["token"],
"nullsNotDistinct": false
}
},
@@ -523,52 +509,36 @@
"board_createdBy_user_id_fk": {
"name": "board_createdBy_user_id_fk",
"tableFrom": "board",
"columnsFrom": [
"createdBy"
],
"columnsFrom": ["createdBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"board_deletedBy_user_id_fk": {
"name": "board_deletedBy_user_id_fk",
"tableFrom": "board",
"columnsFrom": [
"deletedBy"
],
"columnsFrom": ["deletedBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"board_importId_import_id_fk": {
"name": "board_importId_import_id_fk",
"tableFrom": "board",
"columnsFrom": [
"importId"
],
"columnsFrom": ["importId"],
"tableTo": "import",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "no action"
},
"board_workspaceId_workspace_id_fk": {
"name": "board_workspaceId_workspace_id_fk",
"tableFrom": "board",
"columnsFrom": [
"workspaceId"
],
"columnsFrom": ["workspaceId"],
"tableTo": "workspace",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@@ -577,9 +547,7 @@
"uniqueConstraints": {
"board_publicId_unique": {
"name": "board_publicId_unique",
"columns": [
"publicId"
],
"columns": ["publicId"],
"nullsNotDistinct": false
}
},
@@ -713,91 +681,63 @@
"card_activity_cardId_card_id_fk": {
"name": "card_activity_cardId_card_id_fk",
"tableFrom": "card_activity",
"columnsFrom": [
"cardId"
],
"columnsFrom": ["cardId"],
"tableTo": "card",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"card_activity_fromListId_list_id_fk": {
"name": "card_activity_fromListId_list_id_fk",
"tableFrom": "card_activity",
"columnsFrom": [
"fromListId"
],
"columnsFrom": ["fromListId"],
"tableTo": "list",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"card_activity_toListId_list_id_fk": {
"name": "card_activity_toListId_list_id_fk",
"tableFrom": "card_activity",
"columnsFrom": [
"toListId"
],
"columnsFrom": ["toListId"],
"tableTo": "list",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"card_activity_labelId_label_id_fk": {
"name": "card_activity_labelId_label_id_fk",
"tableFrom": "card_activity",
"columnsFrom": [
"labelId"
],
"columnsFrom": ["labelId"],
"tableTo": "label",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"card_activity_workspaceMemberId_workspace_members_id_fk": {
"name": "card_activity_workspaceMemberId_workspace_members_id_fk",
"tableFrom": "card_activity",
"columnsFrom": [
"workspaceMemberId"
],
"columnsFrom": ["workspaceMemberId"],
"tableTo": "workspace_members",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"card_activity_createdBy_user_id_fk": {
"name": "card_activity_createdBy_user_id_fk",
"tableFrom": "card_activity",
"columnsFrom": [
"createdBy"
],
"columnsFrom": ["createdBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"card_activity_commentId_card_comments_id_fk": {
"name": "card_activity_commentId_card_comments_id_fk",
"tableFrom": "card_activity",
"columnsFrom": [
"commentId"
],
"columnsFrom": ["commentId"],
"tableTo": "card_comments",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@@ -806,9 +746,7 @@
"uniqueConstraints": {
"card_activity_publicId_unique": {
"name": "card_activity_publicId_unique",
"columns": [
"publicId"
],
"columns": ["publicId"],
"nullsNotDistinct": false
}
},
@@ -838,26 +776,18 @@
"_card_workspace_members_cardId_card_id_fk": {
"name": "_card_workspace_members_cardId_card_id_fk",
"tableFrom": "_card_workspace_members",
"columnsFrom": [
"cardId"
],
"columnsFrom": ["cardId"],
"tableTo": "card",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"_card_workspace_members_workspaceMemberId_workspace_members_id_fk": {
"name": "_card_workspace_members_workspaceMemberId_workspace_members_id_fk",
"tableFrom": "_card_workspace_members",
"columnsFrom": [
"workspaceMemberId"
],
"columnsFrom": ["workspaceMemberId"],
"tableTo": "workspace_members",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@@ -865,10 +795,7 @@
"compositePrimaryKeys": {
"_card_workspace_members_cardId_workspaceMemberId_pk": {
"name": "_card_workspace_members_cardId_workspaceMemberId_pk",
"columns": [
"cardId",
"workspaceMemberId"
]
"columns": ["cardId", "workspaceMemberId"]
}
},
"uniqueConstraints": {},
@@ -959,52 +886,36 @@
"card_createdBy_user_id_fk": {
"name": "card_createdBy_user_id_fk",
"tableFrom": "card",
"columnsFrom": [
"createdBy"
],
"columnsFrom": ["createdBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"card_deletedBy_user_id_fk": {
"name": "card_deletedBy_user_id_fk",
"tableFrom": "card",
"columnsFrom": [
"deletedBy"
],
"columnsFrom": ["deletedBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"card_listId_list_id_fk": {
"name": "card_listId_list_id_fk",
"tableFrom": "card",
"columnsFrom": [
"listId"
],
"columnsFrom": ["listId"],
"tableTo": "list",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"card_importId_import_id_fk": {
"name": "card_importId_import_id_fk",
"tableFrom": "card",
"columnsFrom": [
"importId"
],
"columnsFrom": ["importId"],
"tableTo": "import",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "no action"
}
@@ -1013,9 +924,7 @@
"uniqueConstraints": {
"card_publicId_unique": {
"name": "card_publicId_unique",
"columns": [
"publicId"
],
"columns": ["publicId"],
"nullsNotDistinct": false
}
},
@@ -1045,26 +954,18 @@
"_card_labels_cardId_card_id_fk": {
"name": "_card_labels_cardId_card_id_fk",
"tableFrom": "_card_labels",
"columnsFrom": [
"cardId"
],
"columnsFrom": ["cardId"],
"tableTo": "card",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"_card_labels_labelId_label_id_fk": {
"name": "_card_labels_labelId_label_id_fk",
"tableFrom": "_card_labels",
"columnsFrom": [
"labelId"
],
"columnsFrom": ["labelId"],
"tableTo": "label",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
}
@@ -1072,10 +973,7 @@
"compositePrimaryKeys": {
"_card_labels_cardId_labelId_pk": {
"name": "_card_labels_cardId_labelId_pk",
"columns": [
"cardId",
"labelId"
]
"columns": ["cardId", "labelId"]
}
},
"uniqueConstraints": {},
@@ -1148,39 +1046,27 @@
"card_comments_cardId_card_id_fk": {
"name": "card_comments_cardId_card_id_fk",
"tableFrom": "card_comments",
"columnsFrom": [
"cardId"
],
"columnsFrom": ["cardId"],
"tableTo": "card",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"card_comments_createdBy_user_id_fk": {
"name": "card_comments_createdBy_user_id_fk",
"tableFrom": "card_comments",
"columnsFrom": [
"createdBy"
],
"columnsFrom": ["createdBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"card_comments_deletedBy_user_id_fk": {
"name": "card_comments_deletedBy_user_id_fk",
"tableFrom": "card_comments",
"columnsFrom": [
"deletedBy"
],
"columnsFrom": ["deletedBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
}
@@ -1189,9 +1075,7 @@
"uniqueConstraints": {
"card_comments_publicId_unique": {
"name": "card_comments_publicId_unique",
"columns": [
"publicId"
],
"columns": ["publicId"],
"nullsNotDistinct": false
}
},
@@ -1253,13 +1137,9 @@
"feedback_createdBy_user_id_fk": {
"name": "feedback_createdBy_user_id_fk",
"tableFrom": "feedback",
"columnsFrom": [
"createdBy"
],
"columnsFrom": ["createdBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
}
@@ -1319,13 +1199,9 @@
"import_createdBy_user_id_fk": {
"name": "import_createdBy_user_id_fk",
"tableFrom": "import",
"columnsFrom": [
"createdBy"
],
"columnsFrom": ["createdBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
}
@@ -1334,9 +1210,7 @@
"uniqueConstraints": {
"import_publicId_unique": {
"name": "import_publicId_unique",
"columns": [
"publicId"
],
"columns": ["publicId"],
"nullsNotDistinct": false
}
},
@@ -1421,52 +1295,36 @@
"label_createdBy_user_id_fk": {
"name": "label_createdBy_user_id_fk",
"tableFrom": "label",
"columnsFrom": [
"createdBy"
],
"columnsFrom": ["createdBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"label_boardId_board_id_fk": {
"name": "label_boardId_board_id_fk",
"tableFrom": "label",
"columnsFrom": [
"boardId"
],
"columnsFrom": ["boardId"],
"tableTo": "board",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"label_importId_import_id_fk": {
"name": "label_importId_import_id_fk",
"tableFrom": "label",
"columnsFrom": [
"importId"
],
"columnsFrom": ["importId"],
"tableTo": "import",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "no action"
},
"label_deletedBy_user_id_fk": {
"name": "label_deletedBy_user_id_fk",
"tableFrom": "label",
"columnsFrom": [
"deletedBy"
],
"columnsFrom": ["deletedBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
}
@@ -1475,9 +1333,7 @@
"uniqueConstraints": {
"label_publicId_unique": {
"name": "label_publicId_unique",
"columns": [
"publicId"
],
"columns": ["publicId"],
"nullsNotDistinct": false
}
},
@@ -1562,52 +1418,36 @@
"list_createdBy_user_id_fk": {
"name": "list_createdBy_user_id_fk",
"tableFrom": "list",
"columnsFrom": [
"createdBy"
],
"columnsFrom": ["createdBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"list_deletedBy_user_id_fk": {
"name": "list_deletedBy_user_id_fk",
"tableFrom": "list",
"columnsFrom": [
"deletedBy"
],
"columnsFrom": ["deletedBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"list_boardId_board_id_fk": {
"name": "list_boardId_board_id_fk",
"tableFrom": "list",
"columnsFrom": [
"boardId"
],
"columnsFrom": ["boardId"],
"tableTo": "board",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"list_importId_import_id_fk": {
"name": "list_importId_import_id_fk",
"tableFrom": "list",
"columnsFrom": [
"importId"
],
"columnsFrom": ["importId"],
"tableTo": "import",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "no action"
}
@@ -1616,9 +1456,7 @@
"uniqueConstraints": {
"list_publicId_unique": {
"name": "list_publicId_unique",
"columns": [
"publicId"
],
"columns": ["publicId"],
"nullsNotDistinct": false
}
},
@@ -1688,9 +1526,7 @@
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"columns": [
"email"
],
"columns": ["email"],
"nullsNotDistinct": false
}
},
@@ -1722,9 +1558,7 @@
"uniqueConstraints": {
"workspace_slugs_slug_unique": {
"name": "workspace_slugs_slug_unique",
"columns": [
"slug"
],
"columns": ["slug"],
"nullsNotDistinct": false
}
},
@@ -1818,39 +1652,27 @@
"workspace_members_userId_user_id_fk": {
"name": "workspace_members_userId_user_id_fk",
"tableFrom": "workspace_members",
"columnsFrom": [
"userId"
],
"columnsFrom": ["userId"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"workspace_members_workspaceId_workspace_id_fk": {
"name": "workspace_members_workspaceId_workspace_id_fk",
"tableFrom": "workspace_members",
"columnsFrom": [
"workspaceId"
],
"columnsFrom": ["workspaceId"],
"tableTo": "workspace",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "cascade"
},
"workspace_members_deletedBy_user_id_fk": {
"name": "workspace_members_deletedBy_user_id_fk",
"tableFrom": "workspace_members",
"columnsFrom": [
"deletedBy"
],
"columnsFrom": ["deletedBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
}
@@ -1859,9 +1681,7 @@
"uniqueConstraints": {
"workspace_members_publicId_unique": {
"name": "workspace_members_publicId_unique",
"columns": [
"publicId"
],
"columns": ["publicId"],
"nullsNotDistinct": false
}
},
@@ -1948,26 +1768,18 @@
"workspace_createdBy_user_id_fk": {
"name": "workspace_createdBy_user_id_fk",
"tableFrom": "workspace",
"columnsFrom": [
"createdBy"
],
"columnsFrom": ["createdBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
},
"workspace_deletedBy_user_id_fk": {
"name": "workspace_deletedBy_user_id_fk",
"tableFrom": "workspace",
"columnsFrom": [
"deletedBy"
],
"columnsFrom": ["deletedBy"],
"tableTo": "user",
"columnsTo": [
"id"
],
"columnsTo": ["id"],
"onUpdate": "no action",
"onDelete": "set null"
}
@@ -1976,16 +1788,12 @@
"uniqueConstraints": {
"workspace_publicId_unique": {
"name": "workspace_publicId_unique",
"columns": [
"publicId"
],
"columns": ["publicId"],
"nullsNotDistinct": false
},
"workspace_slug_unique": {
"name": "workspace_slug_unique",
"columns": [
"slug"
],
"columns": ["slug"],
"nullsNotDistinct": false
}
},
@@ -1998,10 +1806,7 @@
"public.board_visibility": {
"name": "board_visibility",
"schema": "public",
"values": [
"private",
"public"
]
"values": ["private", "public"]
},
"public.card_activity_type": {
"name": "card_activity_type",
@@ -2025,53 +1830,32 @@
"public.source": {
"name": "source",
"schema": "public",
"values": [
"trello"
]
"values": ["trello"]
},
"public.status": {
"name": "status",
"schema": "public",
"values": [
"started",
"success",
"failed"
]
"values": ["started", "success", "failed"]
},
"public.role": {
"name": "role",
"schema": "public",
"values": [
"admin",
"member",
"guest"
]
"values": ["admin", "member", "guest"]
},
"public.member_status": {
"name": "member_status",
"schema": "public",
"values": [
"invited",
"active",
"removed"
]
"values": ["invited", "active", "removed"]
},
"public.slug_type": {
"name": "slug_type",
"schema": "public",
"values": [
"reserved",
"premium"
]
"values": ["reserved", "premium"]
},
"public.workspace_plan": {
"name": "workspace_plan",
"schema": "public",
"values": [
"free",
"pro",
"enterprise"
]
"values": ["free", "pro", "enterprise"]
}
},
"schemas": {},
@@ -2084,4 +1868,4 @@
"schemas": {},
"tables": {}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -50,6 +50,20 @@
"when": 1749585889984,
"tag": "20250610200449_ReaddIntegrationTable",
"breakpoints": true
},
{
"idx": 7,
"version": "7",
"when": 1754511666265,
"tag": "20250806202106_glossy_luminals",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1755094668761,
"tag": "20250813141748_AddChecklistActivityTypes",
"breakpoints": true
}
]
}
}

View File

@@ -39,6 +39,7 @@
"with-env": "dotenv -e ../../.env --"
},
"dependencies": {
"@electric-sql/pglite": "^0.3.7",
"@kan/shared": "workspace:^",
"drizzle-orm": "^0.42.0",
"drizzle-zod": "^0.5.1",

View File

@@ -1,8 +1,12 @@
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
import { drizzle as drizzlePgLite } from "drizzle-orm/pglite";
import { uuid_ossp } from "@electric-sql/pglite/contrib/uuid_ossp";
import { drizzle as drizzlePg } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";
import { PGlite } from "@electric-sql/pglite";
import { migrate } from "drizzle-orm/pglite/migrator";
export type dbClient = NodePgDatabase<typeof schema> & {
$client: Pool;
@@ -12,7 +16,14 @@ export const createDrizzleClient = (): dbClient => {
const connectionString = process.env.POSTGRES_URL;
if (!connectionString) {
throw new Error("POSTGRES_URL environment variable is not set");
console.log("POSTGRES_URL environment variable is not set, using PGLite");
const client = new PGlite({ dataDir: "./pgdata", extensions: { uuid_ossp }});
const db = drizzlePgLite(client, { schema });
migrate(db, { migrationsFolder: "../../packages/db/migrations" });
return db as unknown as dbClient;
}
const pool = new Pool({

View File

@@ -1,4 +1,4 @@
import { and, asc, desc, eq, exists, inArray, isNull, or, sql } from "drizzle-orm";
import { and, asc, desc, eq, inArray, isNull, or } from "drizzle-orm";
import type { dbClient } from "@kan/db/client";
import type { BoardVisibilityStatus } from "@kan/db/schema";
@@ -7,6 +7,8 @@ import {
cards,
cardsToLabels,
cardToWorkspaceMembers,
checklistItems,
checklists,
labels,
lists,
workspaceMembers,
@@ -164,6 +166,27 @@ export const getByPublicId = async (
},
},
},
checklists: {
columns: {
publicId: true,
name: true,
index: true,
},
where: isNull(checklists.deletedAt),
orderBy: asc(checklists.index),
with: {
items: {
columns: {
publicId: true,
title: true,
completed: true,
index: true,
},
where: isNull(checklistItems.deletedAt),
orderBy: asc(checklistItems.index),
},
},
},
},
where: and(
cardIds.length > 0 ? inArray(cards.publicId, cardIds) : undefined,
@@ -201,6 +224,7 @@ export const getByPublicId = async (
export const getBySlug = async (
db: dbClient,
boardSlug: string,
workspaceId: number,
filters: {
members: string[];
labels: string[];
@@ -279,6 +303,27 @@ export const getBySlug = async (
},
},
},
checklists: {
columns: {
publicId: true,
name: true,
index: true,
},
where: isNull(checklists.deletedAt),
orderBy: asc(checklists.index),
with: {
items: {
columns: {
publicId: true,
title: true,
completed: true,
index: true,
},
where: isNull(checklistItems.deletedAt),
orderBy: asc(checklistItems.index),
},
},
},
},
where: and(
cardIds.length > 0 ? inArray(cards.publicId, cardIds) : undefined,
@@ -293,6 +338,7 @@ export const getBySlug = async (
},
where: and(
eq(boards.slug, boardSlug),
eq(boards.workspaceId, workspaceId),
isNull(boards.deletedAt),
eq(boards.visibility, "public"),
),
@@ -481,30 +527,18 @@ export const getWorkspaceAndBoardIdByBoardPublicId = async (
export const isBoardSlugAvailable = async (
db: dbClient,
boardSlug: string,
boardPublicId: string,
workspaceId: number,
) => {
const result = await db
.select({ id: boards.id })
.from(boards)
.where(
and(
eq(boards.publicId, boardPublicId),
exists(
db
.select({ id: boards.id })
.from(boards)
.where(
and(
eq(boards.slug, boardSlug),
eq(boards.workspaceId, sql`${boards.workspaceId}`), // Reference outer query's workspaceId
isNull(boards.deletedAt),
),
)
.limit(1),
),
),
)
.limit(1);
const result = await db.query.boards.findFirst({
columns: {
id: true,
},
where: and(
eq(boards.slug, boardSlug),
eq(boards.workspaceId, workspaceId),
isNull(boards.deletedAt),
),
});
return result.length === 0;
return result === undefined;
};

View File

@@ -6,6 +6,8 @@ import {
cards,
cardsToLabels,
cardToWorkspaceMembers,
checklistItems,
checklists,
labels,
lists,
workspaceMembers,
@@ -300,6 +302,27 @@ export const getWithListAndMembersByPublicId = async (
},
},
},
checklists: {
columns: {
publicId: true,
name: true,
index: true,
},
where: isNull(checklists.deletedAt),
orderBy: asc(checklists.index),
with: {
items: {
columns: {
publicId: true,
title: true,
completed: true,
index: true,
},
where: isNull(checklistItems.deletedAt),
orderBy: asc(checklistItems.index),
},
},
},
list: {
columns: {
publicId: true,

View File

@@ -0,0 +1,216 @@
import { and, desc, eq, isNull } from "drizzle-orm";
import type { dbClient } from "@kan/db/client";
import { checklistItems, checklists } from "@kan/db/schema";
import { generateUID } from "@kan/shared/utils";
export const create = async (
db: dbClient,
checklistInput: {
cardId: number;
name: string;
createdBy: string;
},
) => {
return db.transaction(async (tx) => {
const card = await tx.query.checklists.findFirst({
where: and(
eq(checklists.cardId, checklistInput.cardId),
isNull(checklists.deletedAt),
),
orderBy: desc(checklists.index),
});
const [result] = await tx
.insert(checklists)
.values({
publicId: generateUID(),
name: checklistInput.name,
createdBy: checklistInput.createdBy,
cardId: checklistInput.cardId,
index: card ? card.index + 1 : 0,
})
.returning({
id: checklists.id,
publicId: checklists.publicId,
name: checklists.name,
});
return result;
});
};
export const createItem = async (
db: dbClient,
checklistItemInput: {
checklistId: number;
title: string;
createdBy: string;
},
) => {
return db.transaction(async (tx) => {
const lastItem = await tx.query.checklistItems.findFirst({
where: and(
eq(checklistItems.checklistId, checklistItemInput.checklistId),
isNull(checklistItems.deletedAt),
),
orderBy: desc(checklistItems.index),
});
const [result] = await tx
.insert(checklistItems)
.values({
publicId: generateUID(),
title: checklistItemInput.title,
createdBy: checklistItemInput.createdBy,
checklistId: checklistItemInput.checklistId,
index: lastItem ? lastItem.index + 1 : 0,
completed: false,
})
.returning({
id: checklistItems.id,
publicId: checklistItems.publicId,
title: checklistItems.title,
completed: checklistItems.completed,
});
return result;
});
};
export const getChecklistByPublicId = async (
db: dbClient,
checklistPublicId: string,
) => {
const checklist = await db.query.checklists.findFirst({
where: and(
eq(checklists.publicId, checklistPublicId),
isNull(checklists.deletedAt),
),
with: {
card: {
with: {
list: {
with: {
board: {
with: {
workspace: true,
},
},
},
},
},
},
},
});
return checklist;
};
export const getChecklistItemByPublicIdWithChecklist = async (
db: dbClient,
checklistItemPublicId: string,
) => {
const item = await db.query.checklistItems.findFirst({
where: and(
eq(checklistItems.publicId, checklistItemPublicId),
isNull(checklistItems.deletedAt),
),
with: {
checklist: {
with: {
card: {
with: {
list: {
with: {
board: {
with: { workspace: true },
},
},
},
},
},
},
},
},
});
return item;
};
export const updateItemById = async (
db: dbClient,
args: { id: number; title?: string; completed?: boolean },
) => {
const [result] = await db
.update(checklistItems)
.set({
...(args.title !== undefined ? { title: args.title } : {}),
...(args.completed !== undefined ? { completed: args.completed } : {}),
updatedAt: new Date(),
})
.where(eq(checklistItems.id, args.id))
.returning({
publicId: checklistItems.publicId,
title: checklistItems.title,
completed: checklistItems.completed,
});
return result;
};
export const softDeleteItemById = async (
db: dbClient,
args: { id: number; deletedAt: Date; deletedBy: string },
) => {
const [result] = await db
.update(checklistItems)
.set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
.where(eq(checklistItems.id, args.id))
.returning({ id: checklistItems.id });
return result;
};
export const softDeleteAllItemsByChecklistId = async (
db: dbClient,
args: { checklistId: number; deletedAt: Date; deletedBy: string },
) => {
const result = await db
.update(checklistItems)
.set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
.where(
and(
eq(checklistItems.checklistId, args.checklistId),
isNull(checklistItems.deletedAt),
),
)
.returning({ id: checklistItems.id });
return result;
};
export const softDeleteById = async (
db: dbClient,
args: { id: number; deletedAt: Date; deletedBy: string },
) => {
const [result] = await db
.update(checklists)
.set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
.where(eq(checklists.id, args.id))
.returning({ id: checklists.id });
return result;
};
export const updateChecklistById = async (
db: dbClient,
args: { id: number; name: string },
) => {
const [result] = await db
.update(checklists)
.set({ name: args.name, updatedAt: new Date() })
.where(eq(checklists.id, args.id))
.returning({ publicId: checklists.publicId, name: checklists.name });
return result;
};

View File

@@ -12,6 +12,7 @@ import {
varchar,
} from "drizzle-orm/pg-core";
import { checklists } from "./checklists";
import { imports } from "./imports";
import { labels } from "./labels";
import { lists } from "./lists";
@@ -31,6 +32,15 @@ export const activityTypes = [
"card.updated.comment.added",
"card.updated.comment.updated",
"card.updated.comment.deleted",
// Checklist activities
"card.updated.checklist.added",
"card.updated.checklist.renamed",
"card.updated.checklist.deleted",
"card.updated.checklist.item.added",
"card.updated.checklist.item.updated",
"card.updated.checklist.item.completed",
"card.updated.checklist.item.uncompleted",
"card.updated.checklist.item.deleted",
"card.archived",
] as const;
@@ -84,6 +94,7 @@ export const cardsRelations = relations(cards, ({ one, many }) => ({
}),
comments: many(comments),
activities: many(cardActivities),
checklists: many(checklists),
}));
export const cardActivities = pgTable("card_activity", {

View File

@@ -0,0 +1,90 @@
import { relations } from "drizzle-orm";
import {
bigint,
bigserial,
boolean,
integer,
pgTable,
timestamp,
uuid,
varchar,
} from "drizzle-orm/pg-core";
import { cards } from "./cards";
import { users } from "./users";
export const checklists = pgTable("card_checklist", {
id: bigserial("id", { mode: "number" }).primaryKey(),
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
name: varchar("name", { length: 255 }).notNull(),
index: integer("index").notNull(),
cardId: bigint("cardId", { mode: "number" })
.notNull()
.references(() => cards.id, { onDelete: "cascade" }),
createdBy: uuid("createdBy").references(() => users.id, {
onDelete: "set null",
}),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt"),
deletedAt: timestamp("deletedAt"),
deletedBy: uuid("deletedBy").references(() => users.id, {
onDelete: "set null",
}),
}).enableRLS();
export const checklistsRelations = relations(checklists, ({ one, many }) => ({
card: one(cards, {
fields: [checklists.cardId],
references: [cards.id],
relationName: "checklistsCard",
}),
createdBy: one(users, {
fields: [checklists.createdBy],
references: [users.id],
relationName: "checklistsCreatedByUser",
}),
deletedBy: one(users, {
fields: [checklists.deletedBy],
references: [users.id],
relationName: "checklistsDeletedByUser",
}),
items: many(checklistItems),
}));
export const checklistItems = pgTable("card_checklist_item", {
id: bigserial("id", { mode: "number" }).primaryKey(),
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
title: varchar("title", { length: 500 }).notNull(),
completed: boolean("completed").notNull().default(false),
index: integer("index").notNull(),
checklistId: bigint("checklistId", { mode: "number" })
.notNull()
.references(() => checklists.id, { onDelete: "cascade" }),
createdBy: uuid("createdBy").references(() => users.id, {
onDelete: "set null",
}),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt"),
deletedAt: timestamp("deletedAt"),
deletedBy: uuid("deletedBy").references(() => users.id, {
onDelete: "set null",
}),
}).enableRLS();
export const checklistItemsRelations = relations(checklistItems, ({ one }) => ({
checklist: one(checklists, {
fields: [checklistItems.checklistId],
references: [checklists.id],
relationName: "checklistItemsChecklist",
}),
createdBy: one(users, {
fields: [checklistItems.createdBy],
references: [users.id],
relationName: "checklistItemsCreatedByUser",
}),
deletedBy: one(users, {
fields: [checklistItems.deletedBy],
references: [users.id],
relationName: "checklistItemsDeletedByUser",
}),
}));

View File

@@ -2,6 +2,7 @@ export * from "./auth";
export * from "./boards";
export * from "./auth";
export * from "./cards";
export * from "./checklists";
export * from "./feedback";
export * from "./imports";
export * from "./labels";

View File

@@ -23,6 +23,7 @@
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
},
"dependencies": {
"@plunk/node": "^3.0.3",
"@react-email/components": "^0.0.25",
"nodemailer": "^7.0.3",
"react-email": "^3.0.1"

View File

@@ -0,0 +1,10 @@
import Plunk from "@plunk/node";
export const cloudMailerClient =
process.env.NEXT_PUBLIC_KAN_ENV === "cloud" &&
process.env.PLUNK_API_KEY &&
process.env.PLUNK_API_URL
? new Plunk(process.env.PLUNK_API_KEY ?? "", {
baseUrl: process.env.PLUNK_API_URL,
})
: null;

View File

@@ -1,3 +1,4 @@
export const name = "email";
export { sendEmail } from "./sendEmail";
export { cloudMailerClient } from "./cloudMailerClient";

View File

@@ -1,6 +1,7 @@
import { render } from "@react-email/render";
import nodemailer from "nodemailer";
import { cloudMailerClient } from "./cloudMailerClient";
import JoinWorkspaceTemplate from "./templates/join-workspace";
import MagicLinkTemplate from "./templates/magic-link";
import ResetPasswordTemplate from "./templates/reset-password";
@@ -44,6 +45,15 @@ export const sendEmail = async (
html,
};
if (cloudMailerClient) {
const response = await cloudMailerClient.emails.send({
...options,
body: html,
});
return response;
}
const response = await transporter.sendMail(options);
if (!response.accepted.length) {

46
pnpm-lock.yaml generated
View File

@@ -123,6 +123,9 @@ importers:
'@tiptap/extension-link':
specifier: ^2.22.2
version: 2.22.2(@tiptap/core@2.14.0(@tiptap/pm@2.14.0))(@tiptap/pm@2.14.0)
'@tiptap/extension-mention':
specifier: ^3.0.9
version: 3.0.9(@tiptap/core@2.14.0(@tiptap/pm@2.14.0))(@tiptap/pm@2.14.0)(@tiptap/suggestion@2.14.0(@tiptap/core@2.14.0(@tiptap/pm@2.14.0))(@tiptap/pm@2.14.0))
'@tiptap/extension-placeholder':
specifier: ^2.14.0
version: 2.14.0(@tiptap/core@2.14.0(@tiptap/pm@2.14.0))(@tiptap/pm@2.14.0)
@@ -360,15 +363,18 @@ importers:
packages/db:
dependencies:
'@electric-sql/pglite':
specifier: ^0.3.7
version: 0.3.7
'@kan/shared':
specifier: workspace:^
version: link:../shared
drizzle-orm:
specifier: ^0.42.0
version: 0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.28.2)(pg@8.13.1)(postgres@3.4.5)
version: 0.42.0(@electric-sql/pglite@0.3.7)(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.28.2)(pg@8.13.1)(postgres@3.4.5)
drizzle-zod:
specifier: ^0.5.1
version: 0.5.1(drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.28.2)(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0)
version: 0.5.1(drizzle-orm@0.42.0(@electric-sql/pglite@0.3.7)(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.28.2)(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0)
pg:
specifier: ^8.11.3
version: 8.13.1
@@ -409,6 +415,9 @@ importers:
packages/email:
dependencies:
'@plunk/node':
specifier: ^3.0.3
version: 3.0.3
'@react-email/components':
specifier: ^0.0.25
version: 0.0.25(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -885,6 +894,9 @@ packages:
'@drizzle-team/brocli@0.10.2':
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
'@electric-sql/pglite@0.3.7':
resolution: {integrity: sha512-5c3mybVrhxu5s47zFZtIGdG8YHkKCBENOmqxnNBjY53ZoDhADY/c5UqBDl159b7qtkzNPtbbb893wL9zi1kAuw==}
'@emnapi/runtime@1.3.1':
resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==}
@@ -2194,6 +2206,9 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@plunk/node@3.0.3':
resolution: {integrity: sha512-f3NT418/EdLjebQ4arAwR64cnz7d5H7i1CeKh8+B0cVBrwzHvF+Q+QkhutF+FAp49yuI/BYc71qQH+RFc2fxxQ==}
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
@@ -2764,6 +2779,13 @@ packages:
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-mention@3.0.9':
resolution: {integrity: sha512-DTQNAQkHZ+7Enlt3KvjqN6eECINlqPpET4Drzwj8Mmz9kMILc87cz3G2cwEKRrS9A1Xn3H3VpWvElWE2Wq9JHw==}
peerDependencies:
'@tiptap/core': ^3.0.9
'@tiptap/pm': ^3.0.9
'@tiptap/suggestion': ^3.0.9
'@tiptap/extension-ordered-list@2.14.0':
resolution: {integrity: sha512-QUZcyuW9AKvSfpFHcGmbyRCqxcpY0VNf0xipEtogxbA+JDDw3ZSPqU1dUgz9wk00RahPTwNDdY5aVjdQ5N4N9Q==}
peerDependencies:
@@ -6478,6 +6500,7 @@ packages:
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
deprecated: The work that was done in this beta branch won't be included in future versions
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
@@ -7846,6 +7869,8 @@ snapshots:
'@drizzle-team/brocli@0.10.2': {}
'@electric-sql/pglite@0.3.7': {}
'@emnapi/runtime@1.3.1':
dependencies:
tslib: 2.8.1
@@ -8910,6 +8935,10 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
'@plunk/node@3.0.3':
dependencies:
tslib: 2.8.1
'@popperjs/core@2.11.8': {}
'@react-aria/focus@3.19.0(react@18.3.1)':
@@ -9602,6 +9631,12 @@ snapshots:
dependencies:
'@tiptap/core': 2.14.0(@tiptap/pm@2.14.0)
'@tiptap/extension-mention@3.0.9(@tiptap/core@2.14.0(@tiptap/pm@2.14.0))(@tiptap/pm@2.14.0)(@tiptap/suggestion@2.14.0(@tiptap/core@2.14.0(@tiptap/pm@2.14.0))(@tiptap/pm@2.14.0))':
dependencies:
'@tiptap/core': 2.14.0(@tiptap/pm@2.14.0)
'@tiptap/pm': 2.14.0
'@tiptap/suggestion': 2.14.0(@tiptap/core@2.14.0(@tiptap/pm@2.14.0))(@tiptap/pm@2.14.0)
'@tiptap/extension-ordered-list@2.14.0(@tiptap/core@2.14.0(@tiptap/pm@2.14.0))':
dependencies:
'@tiptap/core': 2.14.0(@tiptap/pm@2.14.0)
@@ -10835,8 +10870,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.28.2)(pg@8.13.1)(postgres@3.4.5):
drizzle-orm@0.42.0(@electric-sql/pglite@0.3.7)(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.28.2)(pg@8.13.1)(postgres@3.4.5):
optionalDependencies:
'@electric-sql/pglite': 0.3.7
'@neondatabase/serverless': 0.10.4
'@types/pg': 8.11.6
'@vercel/postgres': 0.10.0(utf-8-validate@6.0.3)
@@ -10844,9 +10880,9 @@ snapshots:
pg: 8.13.1
postgres: 3.4.5
drizzle-zod@0.5.1(drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.28.2)(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0):
drizzle-zod@0.5.1(drizzle-orm@0.42.0(@electric-sql/pglite@0.3.7)(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.28.2)(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0):
dependencies:
drizzle-orm: 0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.28.2)(pg@8.13.1)(postgres@3.4.5)
drizzle-orm: 0.42.0(@electric-sql/pglite@0.3.7)(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.28.2)(pg@8.13.1)(postgres@3.4.5)
zod: 3.24.0
dunder-proto@1.0.0:

View File

@@ -93,6 +93,7 @@
"SMTP_PASSWORD",
"SMTP_SECURE",
"NEXT_PUBLIC_KAN_ENV",
"NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY",
"STRIPE_SECRET_KEY",
"STRIPE_WEBHOOK_SECRET",
"STRIPE_PRO_PLAN_PRICE_ID",
@@ -109,7 +110,9 @@
"S3_FORCE_PATH_STYLE",
"PORT",
"BETTER_AUTH_SECRET",
"BETTER_AUTH_TRUSTED_ORIGINS"
"BETTER_AUTH_TRUSTED_ORIGINS",
"PLUNK_API_KEY",
"PLUNK_API_URL"
],
"globalPassThroughEnv": [
"NODE_ENV",