Compare commits

...
Author SHA1 Message Date
Maksim Eltyshev a0f584e814 chore: Update version 2024-09-16 21:21:30 +02:00
Maksim Eltyshev 135f5eccb5 chore: Bump express and related dependencies 2024-09-16 13:26:29 +02:00
Maksim Eltyshev 10001db8cf chore: Update dependencies 2024-09-16 12:04:56 +02:00
Maksim Eltyshev 7ee351fe30 fix: Preserve extension for attachments with long filename
Closes #77
2024-09-16 00:45:53 +02:00
Maksim Eltyshev 73746b83d1 chore: Bump custom-ui css 2024-09-16 00:18:08 +02:00
Aline Fauquette f9a7b38ce9 feat: Change update strategy in case of persistence in Helm (#876) 2024-09-13 22:54:46 +02:00
dependabot[bot] 1f261fe7b6 chore(deps): Bump micromatch from 4.0.7 to 4.0.8 in /client (#879)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 22:43:00 +02:00
dependabot[bot] 80bee30463 chore(deps): Bump serve-static and express in /client (#878)
Bumps [serve-static](https://github.com/expressjs/serve-static) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `serve-static` from 1.15.0 to 1.16.2
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...v1.16.2)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: serve-static
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 22:42:49 +02:00
dependabot[bot] a9957481b6 chore(deps): Bump send and express in /client (#877)
Bumps [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 22:42:39 +02:00
dependabot[bot] 36b05a5478 chore(deps): Bump webpack from 5.91.0 to 5.94.0 in /client (#864)
Bumps [webpack](https://github.com/webpack/webpack) from 5.91.0 to 5.94.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.91.0...v5.94.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 22:42:08 +02:00
dependabot[bot] af91512abf chore(deps): Bump micromatch from 4.0.7 to 4.0.8 (#874)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 22:39:40 +02:00
Maël Gangloff 8fd0f682d9 feat: Google Chat notifications (#867) 2024-09-04 15:33:43 +02:00
Maksim Eltyshev 9699fbe76a feat: Additional httpOnly token for enhanced security in browsers 2024-09-01 09:31:04 +02:00
Aurélien Troncy 4176a62f1a fix: Update French translation (#862) 2024-08-30 13:27:39 +02:00
Tyler Sweat c12f0844d7 fix: Update Spanish translation (#861) 2024-08-30 11:50:45 +02:00
Aurélien Troncy e6644eb745 feat: Ability to show detailed auth errors, set to false by default (#860) 2024-08-30 11:47:29 +02:00
Blyamur b2e1fba9a0 fix: Update Russian translation (#857) 2024-08-27 16:09:31 +02:00
Maksim Eltyshev 818ae4a6df chore: Update version 2024-08-14 21:39:43 +02:00
Aurélien Troncy 0de0e809ac fix: Update French translation (#849) 2024-08-14 21:37:02 +02:00
Maksim Eltyshev da727c0a0b chore: Generate separate file to store version
Closes #848
2024-08-13 22:26:44 +02:00
Ken 4841a65c77 feat: Add Traditional Chinese translation (#846) 2024-08-13 12:53:42 +02:00
Maksim Eltyshev 0ec4b619d9 chore: Update version 2024-08-12 23:23:50 +02:00
Maksim Eltyshev 12f05adde7 fix: Fix styles, refactoring 2024-08-12 23:17:17 +02:00
Arkadiusz Dzięgiel c594e8bd71 feat: Colorize due date and make it toggleable (#845) 2024-08-12 18:29:50 +02:00
Maksim Eltyshev f84166406f ref: Refactoring 2024-07-26 20:51:59 +02:00
Fayad 82b2ba298d feat: Improvements for mobile version (#776) 2024-07-26 19:02:13 +02:00
jaycoolslm 38b6805ba1 fix: Update healthcheck parameters in docker-compose-dev.yml (#834) 2024-07-24 13:10:56 +02:00
dependabot[bot] 6a7a582045 chore(deps): Bump socket.io-parser from 3.3.3 to 3.3.4 in /client (#833)
Bumps [socket.io-parser](https://github.com/Automattic/socket.io-parser) from 3.3.3 to 3.3.4.
- [Release notes](https://github.com/Automattic/socket.io-parser/releases)
- [Changelog](https://github.com/socketio/socket.io-parser/blob/3.3.4/CHANGELOG.md)
- [Commits](https://github.com/Automattic/socket.io-parser/compare/3.3.3...3.3.4)

---
updated-dependencies:
- dependency-name: socket.io-parser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 12:50:41 +02:00
Blyamur c53a00b4ba fix: Update Russian translation (#832) 2024-07-22 16:32:12 +02:00
Blyamur 52103c775b fix: Update Russian translation (#831) 2024-07-22 16:30:44 +02:00
Maksim Eltyshev eeb1bd80cc chore: Update version 2024-07-21 19:44:49 +02:00
dependabot[bot] e6b8538863 chore(deps): Bump ws, engine.io and socket.io-adapter in /server (#830)
Bumps [ws](https://github.com/websockets/ws), [engine.io](https://github.com/socketio/engine.io) and [socket.io-adapter](https://github.com/socketio/socket.io-adapter). These dependencies needed to be updated together.

Updates `ws` from 8.11.0 to 8.17.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.11.0...8.17.1)

Updates `engine.io` from 6.5.4 to 6.5.5
- [Release notes](https://github.com/socketio/engine.io/releases)
- [Changelog](https://github.com/socketio/engine.io/blob/6.5.5/CHANGELOG.md)
- [Commits](https://github.com/socketio/engine.io/compare/6.5.4...6.5.5)

Updates `socket.io-adapter` from 2.5.4 to 2.5.5
- [Release notes](https://github.com/socketio/socket.io-adapter/releases)
- [Changelog](https://github.com/socketio/socket.io-adapter/blob/2.5.5/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io-adapter/compare/2.5.4...2.5.5)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
- dependency-name: engine.io
  dependency-type: indirect
- dependency-name: socket.io-adapter
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-21 19:42:36 +02:00
Maksim Eltyshev 9dc38932fb feat: Languages with country codes 2024-07-21 19:33:57 +02:00
Aurélien Troncy e9b4a4adfc fix: Disable x-powered-by header (#829) 2024-07-21 18:27:26 +02:00
Mazyar Yousefiniyae shad 76c78c7252 feat: Add Persian translation (#828) 2024-07-21 18:21:18 +02:00
Maksim Eltyshev 04f52a63f2 chore: Update version 2024-07-17 14:03:00 +02:00
Maksim Eltyshev 6b16a104b1 docs: Update docker pulls counter 2024-07-16 12:39:57 +02:00
Maksim Eltyshev aff853c602 ref: Refactoring, fix linting 2024-07-16 12:33:38 +02:00
aleb_the_flash ad2966c5d6 feat: Improve OIDC support for strict providers (#824) 2024-07-16 12:19:27 +02:00
Maksim Eltyshev 8d74cc1732 chore: Update version 2024-07-15 16:24:31 +02:00
Emmanuel Guyot ea94fe8bf3 fix: Order users by name (#806) 2024-07-15 15:26:26 +02:00
Αλέξανδρος 6052f8999f fix: Fix application crashing when webhook is unavailable (#815) 2024-07-15 14:40:33 +02:00
ItzAndriss 67d1b1c194 fix: Update Hungarian translation (#823) 2024-07-15 14:25:31 +02:00
Stephan Michard b3cb8405f5 fix: Update German translation (#821) 2024-07-15 14:19:06 +02:00
Maksim Eltyshev 7acaec6d04 fix: Fix incorrect related data when transferring card
Closes #431, closes #803
2024-07-15 14:01:06 +02:00
Maksim Eltyshev 72896a66b3 chore: Update version 2024-06-21 12:11:15 +02:00
Maksim Eltyshev 4be4783251 fix: Fix icon spacing for action buttons 2024-06-21 12:01:46 +02:00
HannesOberreiter 5c6c6a0bcf feat: Add copy link action to card modal (#804) 2024-06-21 11:48:36 +02:00
HannesOberreiter ba178d66fa docs: Add types to webhook function (#796) 2024-06-18 15:41:12 +02:00
HannesOberreiter 270ce29f87 feat: Add custom user agent with base url to webhook (#799) 2024-06-18 15:36:51 +02:00
HannesOberreiter de1ed3c784 ref: Define all possible event types in webhook module (#795) 2024-06-18 15:32:39 +02:00
Maksim Eltyshev caf8790f28 chore: Update version 2024-06-14 17:18:42 +02:00
Sarah Soo 81436b373d feat: Ability to specify existing secrets for values in Helm (#791)
Closes #790
2024-06-14 16:41:10 +02:00
NathanVss cc1e886a31 feat: Ability to allow everyone to create projects (#787) 2024-06-14 16:38:06 +02:00
Maksim Eltyshev f0e9fe7904 chore: Update version 2024-06-12 09:06:20 +02:00
Gavin Mogan 5fcea5d651 fix: Handle WEBHOOKS env variable being unset (#785)
Closes #784
2024-06-12 08:59:36 +02:00
Maksim Eltyshev a963d85a66 chore: Update version 2024-06-12 01:02:55 +02:00
Samuel 04b42decef meta: Add issue templates (#782) 2024-06-12 00:59:37 +02:00
Maksim Eltyshev c065566c15 feat: Webhooks configuration, all events support, refactoring 2024-06-12 00:51:36 +02:00
HannesOberreiter 3779bdb053 feat: Events via webhook (#771)
Closes #215, closes #656
2024-06-06 20:22:14 +02:00
HannesOberreiter 4124ab17d2 fix: Focus end of field when editing (#779) 2024-06-06 20:06:16 +02:00
Maksim Eltyshev 0e3bc92a61 chore: Update version 2024-06-02 01:36:29 +02:00
Maksim Eltyshev b8d7e713b9 chore: Update dependencies 2024-06-02 01:34:03 +02:00
Emmanuel Guyot 017eee8302 fix: Use server base path for socket connection (#772)
Closes #768
2024-05-23 23:59:44 +02:00
Mitch Harvey 1b28892cf5 fix: Fix Helm template for dburl secret (#770) 2024-05-21 23:32:07 +02:00
Maksim Eltyshev 013ea1b869 chore: Update version 2024-05-21 13:58:28 +02:00
Blyamur 4975fa2eeb fix: Update Russian translation (#767) 2024-05-21 13:51:57 +02:00
Blyamur 3e28ad87c7 fix: Update Russian translation (#766) 2024-05-21 13:51:34 +02:00
Smiley3112 2d6666d693 feat: Add SMTP_NAME environment variable (#761)
Closes #758
2024-05-18 15:02:21 +02:00
Maksim Eltyshev 4cbd2f85c7 chore: Update version 2024-05-16 21:06:30 +02:00
Maksim Eltyshev fae9de4e1c fix: Prevent loading fonts from third-party resources
Closes #538, closes #712, closes #742, closes #755
2024-05-16 21:02:58 +02:00
IT Creativity + Art Team 89c1ed71e1 feat: Add Bulgarian translation (#753) 2024-05-13 13:05:20 +02:00
Mitch Harvey 1725f3cb8a feat: Ability to specify existing secret as database url (#751)
Closes #750
2024-05-13 11:07:57 +02:00
Christopher Greaves f7be49e402 docs: Update Kubernetes README (#740) 2024-05-03 21:04:34 +02:00
Maksim Eltyshev c8d732d936 chore: Update version 2024-05-03 12:44:51 +02:00
Maksim Eltyshev 5a32b6327c fix: Fix actions with members
Closes #737
2024-05-03 12:42:51 +02:00
Emmanuel Guyot 8488105810 feat: Search in card descriptions (#729) 2024-04-26 21:52:57 +02:00
Niccolò Pedrini 57ebfa51d8 fix: Update Italian translation (#727) 2024-04-24 18:49:27 +02:00
Maksim Eltyshev 40170fdf90 chore: Update version 2024-04-23 15:57:14 +02:00
leroyloren 1629f62d09 fix: Update Czech translation (#725) 2024-04-23 15:53:10 +02:00
Maksim Eltyshev fff0552081 chore: Update dependencies
Closes #726
2024-04-23 15:45:47 +02:00
Maksim Eltyshev e792cb26b1 chore: Update version 2024-04-22 23:25:09 +02:00
Maksim Eltyshev 7ee2d76be4 feat: Display avatar next to user name in top bar 2024-04-22 23:23:36 +02:00
Emmanuel Guyot eb56b2147b feat: Filter cards by keyword with advanced capabilities (#713)
Closes #706
2024-04-22 23:15:31 +02:00
Maksim Eltyshev 8747aa59de fix: Fix title of sort menu item 2024-04-22 22:02:51 +02:00
Samuel 934dcdf39b feat: Sort cards within list (#717)
Closes #390
2024-04-22 21:56:07 +02:00
Felipe 2c84316fe0 fix: Limit amount of displayed board members (#715)
Closes #617
2024-04-22 15:28:57 +02:00
Daniel Hiller 03825c3bed CI: Fix dev Build 2024-04-16 03:43:54 +02:00
ItzAndriss 200ea3bbc0 feat: Add Hungarian translation (#590) 2024-04-15 18:30:18 +02:00
Robson Ventura 5b4b0cd635 feat: Development environment with docker compose (#709) 2024-04-15 16:22:51 +02:00
Gavin Mogan a1323cd516 feat: Set labels when building docker image 2024-04-15 16:19:53 +02:00
Maksim Eltyshev 92f75789fa chore: Update version 2024-04-12 12:09:22 +02:00
Maksim Eltyshev 8a8c1fee0c fix: Fix error output when sending email or message to Slack 2024-04-12 12:07:19 +02:00
Maksim Eltyshev 16499052f7 chore: Update version 2024-04-10 15:57:15 +02:00
HannesOberreiter cc32daa009 feat: Display clickable links in tasks (#694)
Closes #330
2024-04-10 15:53:05 +02:00
Stan M ea1b3b7f92 feat: Ability to run with read-only root filesystem in Helm (#695) 2024-04-10 12:56:21 +02:00
Maksim Eltyshev 4e2863faa7 feat: Automatic logout when session expires
Closes #693
2024-04-09 15:12:46 +02:00
Maksim Eltyshev b46fb43e6f chore: Cleanup 2024-04-08 01:47:24 +02:00
Maksim Eltyshev bcacfa8fe2 Merge branch 'GlitchSecure-slack-bot-integration' 2024-04-08 01:29:28 +02:00
Niccolò Pedrini 4df2becd6f fix: Update Italian translation (#688) 2024-04-08 01:27:19 +02:00
Matthieu Bollot 2990ea593a feat: Slack bot notifications (#676) 2024-04-08 01:27:10 +02:00
Blyamur b976673826 fix: Update Russian translation (#686) 2024-04-08 01:24:50 +02:00
leroyloren 16e034efcb fix: Update Czech translation (#679) 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 6712625c3f chore: Update version 2024-04-08 01:24:50 +02:00
Matthieu Bollot b5bbf6a6a4 feat: Add ability to duplicate card (#668) 2024-04-08 01:24:50 +02:00
Brian Stinson 3d3e7761f5 fix: Allow specifying external dburl in Helm (#672) 2024-04-08 01:24:50 +02:00
piero fa56b9c01f fix: Update HorizontalPodAutoscaler version and spec in chart (#666)
Closes #667
2024-04-08 01:24:50 +02:00
Maksim Eltyshev 882213784f chore: Update version 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 251bd793f3 fix: Fix OIDC authentication error when redirecting from another tab
Closes #650
2024-04-08 01:24:50 +02:00
Maksim Eltyshev 088b0e760b fix: Fix scrollbar styles 2024-04-08 01:24:50 +02:00
Maksim Eltyshev cc1fa1cc3b chore: Update version 2024-04-08 01:24:50 +02:00
Edouard bcd3ea86e8 feat: SMTP integration and email notifications (#631) 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 0176650f67 fix: Add date-fns locale to Portuguese translation 2024-04-08 01:24:50 +02:00
Jeffrey f3963aded3 feat: Add Dutch translation (#644) 2024-04-08 01:24:50 +02:00
Reuben Hu bbe720481a feat: Add Indonesian translation (#643) 2024-04-08 01:24:50 +02:00
IV16SL 04f9030474 fix: Fix Chinese translation (#641) 2024-04-08 01:24:50 +02:00
Chris 6d135be859 feat: Add Portuguese translation (#640) 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 63cbe7063e docs: Update features, add contributors 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 7985bea2b8 chore: Update version 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 733abccaa9 fix: Fix case sensitivity of default admin environment variables 2024-04-08 01:24:50 +02:00
Daniel Hiller 119cce82b4 fix: Container build windows 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 988e70ec40 fix: Fix nullable boolean inputs 2024-04-08 01:24:50 +02:00
Daniel Hiller 5e3fca3771 Update build-and-push-docker-image-dev.yml 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 6d9ce56f4a fix: Include due date when importing from Trello
Closes #598
2024-04-08 01:24:50 +02:00
Daniel Hiller 3c8c04fd37 ci: update workflow 2024-04-08 01:24:50 +02:00
Daniel Hiller 790a1d5cc9 feat: Add healthcheck for docker deployments
fix: Missing healthcheck.js

fix: missing healthcheck.js

fix: HEALTHCHECK command
2024-04-08 01:24:50 +02:00
Maksim Eltyshev 4b352fd878 fix: Remove overflow of attachment dropzone 2024-04-08 01:24:50 +02:00
Maksim Eltyshev a196f2ef11 chore: Update version 2024-04-08 01:24:50 +02:00
Maksim Eltyshev 6c65d135f7 feat: Add ability to enforce SSO
Closes #543, closes #545
2024-04-08 01:24:10 +02:00
Maksim Eltyshev a1a1e9a86a chore: Update version 2024-04-08 01:18:26 +02:00
Maksim Eltyshev 0d39a7567f feat: Add ability to map OIDC attributes and ignore username
Closes #554
2024-04-08 01:18:26 +02:00
Maksim Eltyshev 76a18d06d6 chore: Update version 2024-04-08 01:18:26 +02:00
Maksim Eltyshev ce52fc5af7 fix: Fix images becoming black and white when resizing
Closes #574, closes #585
2024-04-08 01:18:26 +02:00
Maksim Eltyshev 0a2863a79c chore: Bump sharp version 2024-04-08 01:18:26 +02:00
Niccolò Pedrini 39e320fe4b fix: Update Italian translation (#688) 2024-04-08 00:50:52 +02:00
Matthieu Bollot 2f3dfe775e feat: Slack bot notifications (#676) 2024-04-08 00:33:29 +02:00
Blyamur 22964cb375 fix: Update Russian translation (#686) 2024-04-07 22:05:22 +02:00
leroyloren fd89cf7f2b fix: Update Czech translation (#679) 2024-04-07 21:58:27 +02:00
Maksim Eltyshev 5acf84c20b chore: Update version 2024-04-05 22:49:02 +02:00
Matthieu Bollot 6cd9da844f feat: Add ability to duplicate card (#668) 2024-04-05 22:40:35 +02:00
Brian Stinson d7847fe89b fix: Allow specifying external dburl in Helm (#672) 2024-04-05 10:38:46 +02:00
piero d033520927 fix: Update HorizontalPodAutoscaler version and spec in chart (#666)
Closes #667
2024-04-03 20:59:23 +02:00
Maksim Eltyshev d911030831 chore: Update version 2024-03-26 14:08:59 +01:00
Maksim Eltyshev 9d95ed6c41 fix: Fix OIDC authentication error when redirecting from another tab
Closes #650
2024-03-26 14:07:07 +01:00
Maksim Eltyshev 1a5a853fa3 fix: Fix scrollbar styles 2024-03-22 14:26:23 +01:00
Maksim Eltyshev bb358e01dd chore: Update version 2024-03-22 00:52:45 +01:00
Edouard 9f0fce098e feat: SMTP integration and email notifications (#631) 2024-03-22 00:14:09 +01:00
Maksim Eltyshev 60ab09771d fix: Add date-fns locale to Portuguese translation 2024-03-21 12:41:31 +01:00
Jeffrey cb3ef783ba feat: Add Dutch translation (#644) 2024-03-21 12:38:26 +01:00
Reuben Hu fe376b10c1 feat: Add Indonesian translation (#643) 2024-03-21 11:46:33 +01:00
IV16SL 786441cda0 fix: Fix Chinese translation (#641) 2024-03-20 19:13:21 +01:00
Chris 0d3cf0a32d feat: Add Portuguese translation (#640) 2024-03-20 14:55:17 +01:00
Maksim Eltyshev b4e01d7256 docs: Update features, add contributors 2024-03-13 11:02:52 +01:00
Maksim Eltyshev 73a641b935 chore: Update version 2024-03-12 20:44:27 +01:00
Maksim Eltyshev 6802a0dc69 fix: Fix case sensitivity of default admin environment variables 2024-03-12 20:40:46 +01:00
Daniel Hiller 81d6c43cd9 fix: Container build windows 2024-02-14 03:15:34 +01:00
Maksim Eltyshev ca6a1a14f5 fix: Fix nullable boolean inputs 2024-02-08 16:42:10 +01:00
Daniel Hiller c096444acb Update build-and-push-docker-image-dev.yml 2024-02-08 15:34:43 +01:00
Maksim Eltyshev 51d05f3cd3 fix: Include due date when importing from Trello
Closes #598
2024-02-08 13:30:07 +01:00
Daniel Hiller 221da3c455 ci: update workflow 2024-02-06 02:07:27 +01:00
Daniel Hiller 92098e136c feat: Add healthcheck for docker deployments
fix: Missing healthcheck.js

fix: missing healthcheck.js

fix: HEALTHCHECK command
2024-02-06 01:51:00 +01:00
Maksim Eltyshev fd5c33dd5d fix: Remove overflow of attachment dropzone 2024-02-05 13:24:50 +01:00
Maksim Eltyshev 0c1c7e2192 chore: Update version 2024-02-01 00:34:31 +01:00
Maksim Eltyshev b8d262f745 feat: Add ability to enforce SSO
Closes #543, closes #545
2024-02-01 00:31:15 +01:00
Maksim Eltyshev a1fd694248 chore: Update version 2024-01-31 15:58:57 +01:00
Maksim Eltyshev 634d6ceab1 feat: Add ability to map OIDC attributes and ignore username
Closes #554
2024-01-25 23:01:59 +01:00
Maksim Eltyshev 31d4d5f38d chore: Update version 2024-01-18 11:00:20 +01:00
Maksim Eltyshev 3f99438402 fix: Fix images becoming black and white when resizing
Closes #574, closes #585
2024-01-18 10:58:26 +01:00
Maksim Eltyshev 7e7727e3e1 chore: Bump sharp version 2024-01-03 13:59:14 +05:00
GlitchWitch 273d994750 Remove unused code and comments 2023-12-28 12:01:38 -06:00
Brad Bahls 86d21d6abd updated to use currentUser.name for messages 2023-12-28 09:03:41 -07:00
GlitchWitch a9c200cc3b Merge branch 'master' into slack-bot-integration 2023-12-23 14:41:54 -06:00
GlitchWitch 3fedc14062 Rename custom.js -> slack.js 2023-12-23 10:52:15 -06:00
GlitchWitch c42e1fb5dd Add slack variables to docker-compose 2023-12-23 10:49:02 -06:00
GlitchWitch 9961340118 Replace plankaProdUrl with one set by environment. Remove hardcoded channel and unused variables. 2023-12-23 10:48:50 -06:00
Brad Bahls f54bd22757 removed test webhook url 2023-12-23 08:58:22 -07:00
Brad Bahls 1043dacd67 added new custom service with slack integration helper functions; added axios package; added notifications for card create, delete, and update (move); added notifications for comment create 2023-12-23 08:52:07 -07:00
Daniel Hiller 616304a1fa Update branch 2023-12-21 00:51:17 +01:00
Daniel Hiller 1e01148138 ci: Add retry on failure 2023-12-20 23:50:51 +01:00
Daniel Hiller ede56e1ae3 ci: Switch to matrix workflow 2023-12-20 23:28:35 +01:00
Maksim Eltyshev b2e0c45539 chore: Update version 2023-12-20 13:49:07 +01:00
Maksim Eltyshev 389648b546 fix: Add missing date formats to Ukrainian translation 2023-12-20 12:58:43 +01:00
Denis Polishchuk f4789c28db feat: Add Ukrainian translation (#571) 2023-12-20 12:39:51 +01:00
ossdate 668092de7d fix: Update Chinese translation (#569) 2023-12-20 12:33:37 +01:00
Yu Inoue 920f9c097d fix: Display year when date is outside of current year (#567) 2023-12-20 12:26:01 +01:00
Maksim Eltyshev da63598de3 chore: Update version 2023-12-11 00:18:14 +01:00
Gilberto Vidal 517cb34cfe fix: Fix eslint rules for Windows (#508) 2023-12-11 00:13:04 +01:00
NavyStack 079b8eba53 fix: Fine-tune Korean and Japanese translations, fix full stops (#561) 2023-12-10 23:47:04 +01:00
Maksim Eltyshev af02cdd8bf docs: Little readme fixes 2023-12-10 22:20:23 +01:00
Maksim Eltyshev e1bc189b88 chore: Bump chart-releaser-action version 2023-12-10 17:55:15 +01:00
Maksim Eltyshev 5f2723623a chore: Update version 2023-12-09 17:23:01 +01:00
Maksim Eltyshev fee6841d0a fix: Attempt to fix error that occurs during reconnection 2023-12-09 17:19:40 +01:00
Maksim Eltyshev ca6fb3b962 fix: Add state parameter to OIDC authorization url
Closes #558
2023-12-09 17:06:45 +01:00
Maksim Eltyshev 28c3f28e01 fix: Add issuer to OIDC callback parameters
Closes #562
2023-12-09 16:28:24 +01:00
Maksim Eltyshev 80d12aaeeb fix: Fix Korean translation linting 2023-12-04 11:42:15 +01:00
NavyStack 89af209a2e fix: Update Korean translation (#560) 2023-12-04 11:40:04 +01:00
Maksim Eltyshev f1fed3e533 build: Stop using base image 2023-12-01 18:02:19 +01:00
Maksim Eltyshev eddeb96b16 fix: Fix Romanian translation linting 2023-11-24 12:43:00 +01:00
Maksim Eltyshev 9e8ff3b579 chore: Revert remark-gfm update 2023-11-23 14:40:44 +01:00
Maksim Eltyshev fc1278011c build: Use pnpm import before installing dependencies 2023-11-23 11:54:28 +01:00
Andrei Covali 24163463fc feat: Add Romanian translation (#555) 2023-11-22 14:58:13 +01:00
388 changed files with 29542 additions and 26721 deletions
+53
View File
@@ -0,0 +1,53 @@
name: "🐛 Bug Report"
description: Report a bug found while using Planka
title: "[Bug]: "
labels: ["Type: Bug", "Status: Triage"]
body:
- type: dropdown
id: issue-type
attributes:
label: Where is the problem occurring?
description: Select the part of the application where you encountered the issue.
options:
- "I encountered the problem while using the application (Frontend)"
- "I encountered the problem while interacting with the server (Backend)"
- "I'm not sure"
- type: dropdown
id: browsers
attributes:
label: What browsers are you seeing the problem on?
multiple: true
options:
- Brave
- Chrome
- Firefox
- Microsoft Edge
- Safari
- Other
- type: textarea
id: current-behavior
attributes:
label: Current behaviour
description: A description of what is currently happening, including screenshots and other useful information (**DO NOT INCLUDE PRIVATE INFORMATION**).
placeholder: Currently...
validations:
required: true
- type: textarea
id: desired-behavior
attributes:
label: Desired behaviour
description: A clear description of what you think should happen.
placeholder: In this situation, I expected ...
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Clearly describe which steps or actions you have taken to arrive at the problem. If you have some experience with the code, please link to the specific pieces of code.
placeholder: I did X, then Y, before arriving at Z, when ERROR ...
validations:
required: true
- type: textarea
id: other
attributes:
label: Other information
description: Any other details?
@@ -0,0 +1,33 @@
name: "✨ Feature Request"
description: Suggest a feature or enhancement to improve Planka.
labels: ["Type: Idea"]
body:
- type: dropdown
id: idea-type
attributes:
label: Is this a feature for the backend or frontend?
multiple: true
options:
- Backend
- Frontend
validations:
required: true
- type: textarea
id: feature
attributes:
label: What would you like?
description: A clear description of the feature or enhancement wanted.
placeholder: I'd like to be able to...
validations:
required: true
- type: textarea
id: reason
attributes:
label: Why is this needed?
description: A clear description of why this would be useful to have.
placeholder: I want this because...
- type: textarea
id: other
attributes:
label: Other information
placeholder: Any other details?
@@ -40,5 +40,5 @@ jobs:
build-args: ALPINE_VERSION=${{ env.ALPINE_VERSION }}
push: true
tags: |
ghcr.io/plankanban/planka:base-latest
ghcr.io/plankanban/planka:base-${{ env.ALPINE_VERSION }}
ghcr.io/${{ github.repository }}:base-latest
ghcr.io/${{ github.repository }}:base-${{ env.ALPINE_VERSION }}
@@ -1,3 +1,4 @@
# https://docs.docker.com/build/ci/github-actions/multi-platform/
name: Build and push Docker DEV image
on:
@@ -8,9 +9,12 @@ on:
- 'docker-*.sh'
- '*.md'
branches: [master]
workflow_dispatch:
env:
REGISTRY_IMAGE: ghcr.io/${{ github.repository }}
jobs:
build-and-push-docker-image-dev:
build:
runs-on: self-hosted
steps:
- name: Checkout
@@ -29,11 +33,21 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate docker image tags
id: metadata
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
tags: |
type=raw,value=dev
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: |
ghcr.io/plankanban/planka:dev
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -31,12 +31,23 @@ jobs:
result-encoding: string
script: return context.payload.release.tag_name.replace('v', '')
- name: Generate docker image tags
id: metadata
uses: docker/metadata-action@v5
with:
images: |
name=ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ steps.set-version.outputs.result }}
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: |
ghcr.io/plankanban/planka:latest
ghcr.io/plankanban/planka:${{ steps.set-version.outputs.result }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
done
- name: Run chart-releaser for stable
uses: helm/chart-releaser-action@v1.5.0
uses: helm/chart-releaser-action@v1.6.0
with:
charts_dir: charts
mark_as_latest: false
Executable → Regular
-2
View File
@@ -1,4 +1,2 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
+19 -6
View File
@@ -1,35 +1,44 @@
FROM ghcr.io/plankanban/planka:base-latest as server-dependencies
FROM node:18-alpine as server-dependencies
RUN apk -U upgrade \
&& apk add build-base python3 \
--no-cache
WORKDIR /app
COPY server/package.json server/package-lock.json .
COPY server/package.json server/package-lock.json ./
RUN npm install npm@latest --global \
&& npm install pnpm --global \
&& pnpm import \
&& pnpm install --prod
FROM node:lts AS client
WORKDIR /app
COPY client/package.json client/package-lock.json .
COPY client/package.json client/package-lock.json ./
RUN npm install npm@latest --global \
&& npm install pnpm --global \
&& pnpm import \
&& pnpm install --prod
COPY client .
RUN DISABLE_ESLINT_PLUGIN=true npm run build
FROM ghcr.io/plankanban/planka:base-latest
FROM node:18-alpine
RUN apk del vips-dependencies --purge
RUN apk -U upgrade \
&& apk add bash \
--no-cache
USER node
WORKDIR /app
COPY --chown=node:node start.sh .
COPY --chown=node:node server .
COPY --chown=node:node healthcheck.js .
RUN mv .env.sample .env
@@ -44,4 +53,8 @@ VOLUME /app/private/attachments
EXPOSE 1337
CMD ["./start.sh"]
HEALTHCHECK --interval=10s --timeout=2s --start-period=15s \
CMD node ./healthcheck.js
CMD [ "bash", "start.sh" ]
+13 -10
View File
@@ -1,7 +1,7 @@
# Planka
#### Elegant open source project tracking
#### Elegant open source project tracking.
![David (path)](https://img.shields.io/github/package-json/v/plankanban/planka) ![Docker Pulls](https://img.shields.io/docker/pulls/meltyshev/planka) ![GitHub](https://img.shields.io/github/license/plankanban/planka)
![David (path)](https://img.shields.io/github/package-json/v/plankanban/planka) ![Docker Pulls](https://img.shields.io/badge/docker_pulls-4M%2B-%23066da5) ![GitHub](https://img.shields.io/github/license/plankanban/planka)
![](https://raw.githubusercontent.com/plankanban/planka/master/demo.gif)
@@ -10,19 +10,18 @@
## Features
- Create projects, boards, lists, cards, labels and tasks
- Add card members, track time, set a due date, add attachments, write comments
- Markdown support in a card description and comment
- Add card members, track time, set due dates, add attachments, write comments
- Markdown support in card description and comments
- Filter by members and labels
- Customize project background
- Customize project backgrounds
- Real-time updates
- User notifications
- Internationalization
- Internal notifications
- Multiple interface languages
- Single sign-on via OpenID Connect
## How to deploy Planka
There are many ways to install Planka
[Check them out](https://docs.planka.cloud/docs/intro)
There are many ways to install Planka, [check them out](https://docs.planka.cloud/docs/intro).
For configuration, please see the [configuration section](https://docs.planka.cloud/docs/category/configuration).
@@ -46,3 +45,7 @@ See the [development section](https://docs.planka.cloud/docs/Development).
## License
Planka is [AGPL-3.0 licensed](https://github.com/plankanban/planka/blob/master/LICENSE).
## Contributors
[![](https://contrib.rocks/image?repo=plankanban/planka)](https://github.com/plankanban/planka/graphs/contributors)
+2 -2
View File
@@ -15,13 +15,13 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.11
version: 0.2.8
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.15.0"
appVersion: "1.22.0"
dependencies:
- alias: postgresql
+9 -4
View File
@@ -14,11 +14,15 @@ If you want to fully uninstall this chart including the data, follow [these step
## Usage
If you just want to spin up an instance using help, please see [these docs](https://docs.planka.cloud/docs/installation/kubernetes/helm_chart/). If you want to make changes to the chart locally, and deploy them, see the below section.
## Local Building and Using the Chart
The basic usage of the chart can be found below:
```bash
git clone https://github.com/Chris-Greaves/planka-helm-chart.git
cd planka-helm-chart
git clone https://github.com/plankanban/planka.git
cd planka/charts/planka
helm dependency build
export SECRETKEY=$(openssl rand -hex 64)
helm install planka . --set secretkey=$SECRETKEY \
@@ -46,7 +50,7 @@ helm install planka . --set secretkey=$SECRETKEY \
--set admin_email="demo@demo.demo" \
--set admin_password="demo" \
--set admin_name="Demo Demo" \
--set admin_username="demo"
--set admin_username="demo" \
--set ingress.enabled=true \
--set ingress.hosts[0].host=planka.example.dev \
@@ -55,7 +59,7 @@ helm install planka . --set secretkey=$SECRETKEY \
--set admin_email="demo@demo.demo" \
--set admin_password="demo" \
--set admin_name="Demo Demo" \
--set admin_username="demo"
--set admin_username="demo" \
--set ingress.enabled=true \
--set ingress.hosts[0].host=planka.example.dev \
--set ingress.tls[0].secretName=planka-tls \
@@ -76,6 +80,7 @@ admin_name: "Demo Demo"
admin_username: "demo"
# Admin user
# Ingress
ingress:
enabled: true
hosts:
+48 -2
View File
@@ -11,6 +11,10 @@ spec:
selector:
matchLabels:
{{- include "planka.selectorLabels" . | nindent 6 }}
{{- if .Values.persistence.enabled }}
strategy:
type: Recreate
{{- end }}
template:
metadata:
{{- with .Values.podAnnotations }}
@@ -55,14 +59,32 @@ spec:
- mountPath: /app/private/attachments
subPath: attachments
name: planka
{{- if .Values.securityContext.readOnlyRootFilesystem }}
- mountPath: /app/logs
subPath: app-logs
name: emptydir
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
env:
{{- if not .Values.postgresql.enabled }}
{{- if .Values.existingDburlSecret }}
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Values.existingDburlSecret }}
key: uri
{{- else }}
- name: DATABASE_URL
value: {{ required "If the included postgresql deployment is disabled you need to provide an existing secret in .Values.existingDburlSecret or define a Database URL in 'dburl'" .Values.dburl }}
{{- end }}
{{- else }}
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: planka-postgresql-svcbind-custom-user
key: uri
{{- end }}
- name: BASE_URL
{{- if .Values.baseUrl }}
value: {{ .Values.baseUrl }}
@@ -72,17 +94,37 @@ spec:
value: http://localhost:3000
{{- end }}
- name: SECRET_KEY
{{- if .Values.existingSecretkeySecret }}
valueFrom:
secretKeyRef:
name: {{ .Values.existingSecretkeySecret }}
key: key
{{- else }}
value: {{ required "A secret key needs to be generated using 'openssl rand -hex 64' and assigned to secretkey." .Values.secretkey }}
{{- end }}
- name: TRUST_PROXY
value: "0"
- name: DEFAULT_ADMIN_EMAIL
value: {{ .Values.admin_email }}
- name: DEFAULT_ADMIN_PASSWORD
value: {{ .Values.admin_password }}
- name: DEFAULT_ADMIN_NAME
value: {{ .Values.admin_name }}
{{- if .Values.existingAdminCredsSecret }}
- name: DEFAULT_ADMIN_USERNAME
valueFrom:
secretKeyRef:
name: {{ .Values.existingAdminCredsSecret }}
key: username
- name: DEFAULT_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.existingAdminCredsSecret }}
key: password
{{- else }}
- name: DEFAULT_ADMIN_USERNAME
value: {{ .Values.admin_username }}
- name: DEFAULT_ADMIN_PASSWORD
value: {{ .Values.admin_password }}
{{- end }}
{{ range $k, $v := .Values.env }}
- name: {{ $k | quote }}
value: {{ $v | quote }}
@@ -134,3 +176,7 @@ spec:
{{- else }}
emptyDir: {}
{{- end }}
{{- if .Values.securityContext.readOnlyRootFilesystem }}
- name: emptydir
emptyDir: {}
{{- end }}
+7 -3
View File
@@ -1,5 +1,5 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "planka.fullname" . }}
@@ -17,12 +17,16 @@ spec:
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}
+19 -1
View File
@@ -17,6 +17,16 @@ fullnameOverride: ""
# Generate a secret using openssl rand -base64 45
secretkey: ""
## @param existingSecretkeySecret Name of an existing secret containing the session key string
## NOTE: Must contain key `key`
## NOTE: When it's set, the secretkey parameter is ignored
existingSecretkeySecret: ""
## @param existingAdminCredsSecret Name of an existing secret containing the admin username and password
## NOTE: Must contain keys `username` and `password`
## NOTE: When it's set, the `admin_username` and `admin_password` parameters are ignored
existingAdminCredsSecret: ""
# Base url for Planka. Will override `ingress.hosts[0].host`
# Defaults to `http://localhost:3000` if ingress is disabled.
baseUrl: ""
@@ -105,12 +115,20 @@ postgresql:
serviceBindings:
enabled: true
## Set this or existingDburlSecret if you disable the built-in postgresql deployment
dburl:
## @param existingDburlSecret Name of an existing secret containing a DBurl connection string
## NOTE: Must contain key `uri`
## NOTE: When it's set, the `dburl` parameter is ignored
##
existingDburlSecret: ""
## PVC-based data storage configuration
persistence:
enabled: false
# existingClaim: netbox-data
# storageClass: "-"
accessMode: ReadWriteOnce
size: 10Gi
-1
View File
@@ -1 +0,0 @@
REACT_APP_VERSION=1.15.0
+6361 -5918
View File
File diff suppressed because it is too large Load Diff
+34 -25
View File
@@ -49,67 +49,76 @@
"**/*.test.js"
]
}
],
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
]
}
},
"dependencies": {
"@juggle/resize-observer": "^3.4.0",
"classnames": "^2.3.2",
"classnames": "^2.5.1",
"date-fns": "^2.30.0",
"dequal": "^2.0.3",
"easymde": "^2.18.0",
"history": "^5.3.0",
"i18next": "^23.7.6",
"i18next-browser-languagedetector": "^7.2.0",
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0",
"initials": "^3.1.2",
"js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0",
"linkify-react": "^4.1.3",
"linkifyjs": "^4.1.3",
"lodash": "^4.17.21",
"nanoid": "^5.0.3",
"nanoid": "^5.0.7",
"node-sass": "^9.0.0",
"photoswipe": "^5.4.2",
"photoswipe": "^5.4.4",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react": "18.2.0",
"react-app-rewired": "^2.2.1",
"react-beautiful-dnd": "^13.1.1",
"react-datepicker": "^4.21.0",
"react-dom": "^18.2.0",
"react-datepicker": "^4.25.0",
"react-dom": "18.2.0",
"react-dropzone": "^14.2.3",
"react-i18next": "^13.5.0",
"react-i18next": "^15.0.2",
"react-input-mask": "^2.0.4",
"react-markdown": "^8.0.7",
"react-photoswipe-gallery": "^2.2.7",
"react-redux": "^8.1.3",
"react-router-dom": "^6.19.0",
"react-router-dom": "^6.26.2",
"react-scripts": "5.0.1",
"react-simplemde-editor": "^5.2.0",
"react-textarea-autosize": "^8.5.3",
"redux": "^4.2.1",
"redux-logger": "^3.0.6",
"redux-orm": "^0.16.2",
"redux-saga": "^1.2.3",
"redux-saga": "^1.3.0",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.0",
"remark-gfm": "^3.0.1",
"reselect": "^4.1.8",
"sails.io.js": "^1.2.1",
"semantic-ui-react": "^2.1.4",
"semantic-ui-react": "^2.1.5",
"socket.io-client": "^2.5.0",
"validator": "^13.11.0",
"whatwg-fetch": "^3.6.19",
"validator": "^13.12.0",
"whatwg-fetch": "^3.6.20",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.1.0",
"@testing-library/user-event": "^14.5.1",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2",
"babel-preset-airbnb": "^5.0.0",
"chai": "^4.3.10",
"eslint": "^8.53.0",
"chai": "^4.5.0",
"eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"react-test-renderer": "^18.2.0"
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react": "^7.36.1",
"eslint-plugin-react-hooks": "^4.6.2",
"react-test-renderer": "18.2.0"
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
+49 -2
View File
@@ -23,10 +23,14 @@ createCard.failure = (localId, error) => ({
},
});
const handleCardCreate = (card) => ({
const handleCardCreate = (card, cardMemberships, cardLabels, tasks, attachments) => ({
type: ActionTypes.CARD_CREATE_HANDLE,
payload: {
card,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
@@ -53,10 +57,43 @@ updateCard.failure = (id, error) => ({
},
});
const handleCardUpdate = (card) => ({
const handleCardUpdate = (card, isFetched, cardMemberships, cardLabels, tasks, attachments) => ({
type: ActionTypes.CARD_UPDATE_HANDLE,
payload: {
card,
isFetched,
cardMemberships,
cardLabels,
tasks,
attachments,
},
});
const duplicateCard = (id, card, taskIds) => ({
type: ActionTypes.CARD_DUPLICATE,
payload: {
id,
card,
taskIds,
},
});
duplicateCard.success = (localId, card, cardMemberships, cardLabels, tasks) => ({
type: ActionTypes.CARD_DUPLICATE__SUCCESS,
payload: {
localId,
card,
cardMemberships,
cardLabels,
tasks,
},
});
duplicateCard.failure = (id, error) => ({
type: ActionTypes.CARD_DUPLICATE__FAILURE,
payload: {
id,
error,
},
});
@@ -89,11 +126,21 @@ const handleCardDelete = (card) => ({
},
});
const filterText = (boardId, text) => ({
type: ActionTypes.TEXT_FILTER_IN_CURRENT_BOARD,
payload: {
boardId,
text,
},
});
export default {
createCard,
handleCardCreate,
updateCard,
handleCardUpdate,
duplicateCard,
deleteCard,
handleCardDelete,
filterText,
};
+4 -2
View File
@@ -47,9 +47,11 @@ initializeCore.fetchConfig = (config) => ({
},
});
const logout = () => ({
const logout = (invalidateAccessToken) => ({
type: ActionTypes.LOGOUT,
payload: {},
payload: {
invalidateAccessToken,
},
});
logout.invalidateAccessToken = () => ({
+34
View File
@@ -60,6 +60,38 @@ const handleListUpdate = (list) => ({
},
});
const sortList = (id, data) => ({
type: ActionTypes.LIST_SORT,
payload: {
id,
data,
},
});
sortList.success = (list, cards) => ({
type: ActionTypes.LIST_SORT__SUCCESS,
payload: {
list,
cards,
},
});
sortList.failure = (id, error) => ({
type: ActionTypes.LIST_SORT__FAILURE,
payload: {
id,
error,
},
});
const handleListSort = (list, cards) => ({
type: ActionTypes.LIST_SORT_HANDLE,
payload: {
list,
cards,
},
});
const deleteList = (id) => ({
type: ActionTypes.LIST_DELETE,
payload: {
@@ -94,6 +126,8 @@ export default {
handleListCreate,
updateList,
handleListUpdate,
sortList,
handleListSort,
deleteList,
handleListDelete,
};
+4 -5
View File
@@ -1,15 +1,14 @@
import http from './http';
import socket from './socket';
/* Actions */
const createAccessToken = (data, headers) => http.post('/access-tokens', data, headers);
const createAccessToken = (data, headers) =>
http.post('/access-tokens?withHttpOnlyToken=true', data, headers);
const exchangeForAccessTokenUsingOidc = (data, headers) =>
http.post('/access-tokens/exchange-using-oidc', data, headers);
http.post('/access-tokens/exchange-using-oidc?withHttpOnlyToken=true', data, headers);
const deleteCurrentAccessToken = (headers) =>
socket.delete('/access-tokens/me', undefined, headers);
const deleteCurrentAccessToken = (headers) => http.delete('/access-tokens/me', undefined, headers);
export default {
createAccessToken,
+7
View File
@@ -57,6 +57,12 @@ const updateCard = (id, data, headers) =>
item: transformCard(body.item),
}));
const duplicateCard = (id, data, headers) =>
socket.post(`/cards/${id}/duplicate`, data, headers).then((body) => ({
...body,
item: transformCard(body.item),
}));
const deleteCard = (id, headers) =>
socket.delete(`/cards/${id}`, undefined, headers).then((body) => ({
...body,
@@ -81,6 +87,7 @@ export default {
getCard,
updateCard,
deleteCard,
duplicateCard,
makeHandleCardCreate,
makeHandleCardUpdate,
makeHandleCardDelete,
+2 -1
View File
@@ -5,7 +5,7 @@ import Config from '../constants/Config';
const http = {};
// TODO: add all methods
['GET', 'POST'].forEach((method) => {
['GET', 'POST', 'DELETE'].forEach((method) => {
http[method.toLowerCase()] = (url, data, headers) => {
const formData =
data &&
@@ -19,6 +19,7 @@ const http = {};
method,
headers,
body: formData,
credentials: 'include',
})
.then((response) =>
response.json().then((body) => ({
+24
View File
@@ -1,4 +1,5 @@
import socket from './socket';
import { transformCard } from './cards';
/* Actions */
@@ -7,10 +8,33 @@ const createList = (boardId, data, headers) =>
const updateList = (id, data, headers) => socket.patch(`/lists/${id}`, data, headers);
const sortList = (id, data, headers) =>
socket.post(`/lists/${id}/sort`, data, headers).then((body) => ({
...body,
included: {
...body.included,
cards: body.included.cards.map(transformCard),
},
}));
const deleteList = (id, headers) => socket.delete(`/lists/${id}`, undefined, headers);
/* Event handlers */
const makeHandleListSort = (next) => (body) => {
next({
...body,
included: {
...body.included,
cards: body.included.cards.map(transformCard),
},
});
};
export default {
createList,
updateList,
sortList,
deleteList,
makeHandleListSort,
};
+1 -1
View File
@@ -13,7 +13,7 @@ io.sails.environment = process.env.NODE_ENV;
const { socket } = io;
socket.path = `${Config.BASE_PATH}/socket.io`;
socket.path = `${Config.SERVER_BASE_PATH}/socket.io`;
socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,8 @@
:global(#app) {
.wrapper {
height: 100%;
max-height: 100vh;
max-width: 100vw;
position: fixed;
width: 100%;
z-index: -1;
@@ -48,16 +48,6 @@
min-width: 100%;
}
.panel {
align-items: center;
display: flex;
margin-bottom: 20px;
}
.panelItem {
margin-right: 20px;
}
.wrapper {
margin: 0 20px;
}
@@ -13,6 +13,7 @@ const BoardActions = React.memo(
labels,
filterUsers,
filterLabels,
filterText,
allUsers,
canEdit,
canEditMemberships,
@@ -27,6 +28,7 @@ const BoardActions = React.memo(
onLabelUpdate,
onLabelMove,
onLabelDelete,
onTextFilterUpdate,
}) => {
return (
<div className={styles.wrapper}>
@@ -46,6 +48,7 @@ const BoardActions = React.memo(
<Filters
users={filterUsers}
labels={filterLabels}
filterText={filterText}
allBoardMemberships={memberships}
allLabels={labels}
canEdit={canEdit}
@@ -57,6 +60,7 @@ const BoardActions = React.memo(
onLabelUpdate={onLabelUpdate}
onLabelMove={onLabelMove}
onLabelDelete={onLabelDelete}
onTextFilterUpdate={onTextFilterUpdate}
/>
</div>
</div>
@@ -71,6 +75,7 @@ BoardActions.propTypes = {
labels: PropTypes.array.isRequired,
filterUsers: PropTypes.array.isRequired,
filterLabels: PropTypes.array.isRequired,
filterText: PropTypes.string.isRequired,
allUsers: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
canEdit: PropTypes.bool.isRequired,
@@ -86,6 +91,7 @@ BoardActions.propTypes = {
onLabelUpdate: PropTypes.func.isRequired,
onLabelMove: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired,
onTextFilterUpdate: PropTypes.func.isRequired,
};
export default BoardActions;
@@ -1,12 +1,15 @@
:global(#app) {
.action {
align-items: center;
display: flex;
flex: 0 0 auto;
margin-right: 20px;
}
.actions {
align-items: center;
display: flex;
gap: 20px;
justify-content: flex-start;
margin: 20px 20px;
}
+65 -1
View File
@@ -1,7 +1,10 @@
import React, { useCallback } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Icon } from 'semantic-ui-react';
import { usePopup } from '../../lib/popup';
import { Input } from '../../lib/custom-ui';
import User from '../User';
import Label from '../Label';
@@ -14,6 +17,7 @@ const Filters = React.memo(
({
users,
labels,
filterText,
allBoardMemberships,
allLabels,
canEdit,
@@ -25,8 +29,17 @@ const Filters = React.memo(
onLabelUpdate,
onLabelMove,
onLabelDelete,
onTextFilterUpdate,
}) => {
const [t] = useTranslation();
const [isSearchFocused, setIsSearchFocused] = useState(false);
const searchFieldRef = useRef(null);
const cancelSearch = useCallback(() => {
onTextFilterUpdate('');
searchFieldRef.current.blur();
}, [onTextFilterUpdate]);
const handleRemoveUserClick = useCallback(
(id) => {
@@ -42,9 +55,39 @@ const Filters = React.memo(
[onLabelRemove],
);
const handleSearchChange = useCallback(
(_, { value }) => {
onTextFilterUpdate(value);
},
[onTextFilterUpdate],
);
const handleSearchFocus = useCallback(() => {
setIsSearchFocused(true);
}, []);
const handleSearchKeyDown = useCallback(
(event) => {
if (event.key === 'Escape') {
cancelSearch();
}
},
[cancelSearch],
);
const handleSearchBlur = useCallback(() => {
setIsSearchFocused(false);
}, []);
const handleCancelSearchClick = useCallback(() => {
cancelSearch();
}, [cancelSearch]);
const BoardMembershipsPopup = usePopup(BoardMembershipsStep);
const LabelsPopup = usePopup(LabelsStep);
const isSearchActive = filterText || isSearchFocused;
return (
<>
<span className={styles.filter}>
@@ -100,6 +143,25 @@ const Filters = React.memo(
</span>
))}
</span>
<span className={styles.filter}>
<Input
ref={searchFieldRef}
value={filterText}
placeholder={t('common.searchCards')}
icon={
isSearchActive ? (
<Icon link name="cancel" onClick={handleCancelSearchClick} />
) : (
'search'
)
}
className={classNames(styles.search, !isSearchActive && styles.searchInactive)}
onFocus={handleSearchFocus}
onKeyDown={handleSearchKeyDown}
onChange={handleSearchChange}
onBlur={handleSearchBlur}
/>
</span>
</>
);
},
@@ -109,6 +171,7 @@ Filters.propTypes = {
/* eslint-disable react/forbid-prop-types */
users: PropTypes.array.isRequired,
labels: PropTypes.array.isRequired,
filterText: PropTypes.string.isRequired,
allBoardMemberships: PropTypes.array.isRequired,
allLabels: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
@@ -121,6 +184,7 @@ Filters.propTypes = {
onLabelUpdate: PropTypes.func.isRequired,
onLabelMove: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired,
onTextFilterUpdate: PropTypes.func.isRequired,
};
export default Filters;
@@ -43,4 +43,36 @@
line-height: 20px;
padding: 2px 12px;
}
.search {
height: 30px;
margin: 0 12px;
transition: width 0.2s ease;
width: 280px;
@media only screen and (max-width: 797px) {
width: 220px;
}
input {
font-size: 13px;
}
}
.searchInactive {
color: #fff;
height: 24px;
width: 220px;
input {
background: rgba(0, 0, 0, 0.24);
border: none;
color: #fff !important;
font-size: 12px;
&::placeholder {
color: #fff;
}
}
}
}
@@ -4,7 +4,6 @@
max-height: 60vh;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: thin;
width: 100%;
&::-webkit-scrollbar {
@@ -85,7 +85,6 @@
height: 56px;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
&:hover {
height: 38px;
@@ -36,6 +36,7 @@ const ActionsStep = React.memo(
onUpdate,
onMove,
onTransfer,
onDuplicate,
onDelete,
onUserAdd,
onUserRemove,
@@ -76,6 +77,11 @@ const ActionsStep = React.memo(
openStep(StepTypes.MOVE);
}, [openStep]);
const handleDuplicateClick = useCallback(() => {
onDuplicate();
onClose();
}, [onDuplicate, onClose]);
const handleDeleteClick = useCallback(() => {
openStep(StepTypes.DELETE);
}, [openStep]);
@@ -207,6 +213,11 @@ const ActionsStep = React.memo(
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleDuplicateClick}>
{t('action.duplicateCard', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
{t('action.deleteCard', {
context: 'title',
@@ -232,6 +243,7 @@ ActionsStep.propTypes = {
onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired,
onDuplicate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onUserAdd: PropTypes.func.isRequired,
onUserRemove: PropTypes.func.isRequired,
+7 -1
View File
@@ -24,6 +24,7 @@ const Card = React.memo(
index,
name,
dueDate,
isDueDateCompleted,
stopwatch,
coverUrl,
boardId,
@@ -41,6 +42,7 @@ const Card = React.memo(
onUpdate,
onMove,
onTransfer,
onDuplicate,
onDelete,
onUserAdd,
onUserRemove,
@@ -119,7 +121,7 @@ const Card = React.memo(
)}
{dueDate && (
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
<DueDate value={dueDate} size="tiny" />
<DueDate value={dueDate} isCompleted={isDueDateCompleted} size="tiny" />
</span>
)}
{stopwatch && (
@@ -185,6 +187,7 @@ const Card = React.memo(
onUpdate={onUpdate}
onMove={onMove}
onTransfer={onTransfer}
onDuplicate={onDuplicate}
onDelete={onDelete}
onUserAdd={onUserAdd}
onUserRemove={onUserRemove}
@@ -219,6 +222,7 @@ Card.propTypes = {
index: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
dueDate: PropTypes.instanceOf(Date),
isDueDateCompleted: PropTypes.bool,
stopwatch: PropTypes.object, // eslint-disable-line react/forbid-prop-types
coverUrl: PropTypes.string,
boardId: PropTypes.string.isRequired,
@@ -238,6 +242,7 @@ Card.propTypes = {
onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired,
onDuplicate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onUserAdd: PropTypes.func.isRequired,
onUserRemove: PropTypes.func.isRequired,
@@ -252,6 +257,7 @@ Card.propTypes = {
Card.defaultProps = {
dueDate: undefined,
isDueDateCompleted: undefined,
stopwatch: undefined,
coverUrl: undefined,
};
@@ -21,6 +21,14 @@
background: #ebeef0;
color: #516b7a;
}
@media only screen and (max-width: 797px) {
&:focus,
&:active {
background: #ebeef0;
color: #516b7a;
}
}
}
.attachment {
@@ -55,6 +63,12 @@
box-shadow: 0 1px 0 #ccc;
position: relative;
@media only screen and (max-width: 797px) {
.target {
opacity: 1;
}
}
&:hover {
background: #f5f6f7;
border-bottom-color: rgba(9, 30, 66, 0.25);
+2 -1
View File
@@ -5,6 +5,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react';
import { useClosableForm, useField } from '../../hooks';
import { focusEnd } from '../../utils/element-helpers';
import styles from './NameEdit.module.scss';
@@ -79,7 +80,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => {
if (isOpened) {
field.current.ref.current.focus();
focusEnd(field.current.ref.current);
}
}, [isOpened]);
+3 -1
View File
@@ -4,6 +4,8 @@ import classNames from 'classnames';
import { Progress } from 'semantic-ui-react';
import { useToggle } from '../../lib/hooks';
import Linkify from '../Linkify';
import styles from './Tasks.module.scss';
const Tasks = React.memo(({ items }) => {
@@ -48,7 +50,7 @@ const Tasks = React.memo(({ items }) => {
key={item.id}
className={classNames(styles.task, item.isCompleted && styles.taskCompleted)}
>
{item.name}
<Linkify linkStopPropagation>{item.name}</Linkify>
</li>
))}
</ul>
@@ -55,8 +55,10 @@
display: block;
font-size: 12px;
line-height: 14px;
overflow: hidden;
padding-bottom: 6px;
padding-left: 14px;
text-overflow: ellipsis;
&:before {
content: "–";
@@ -6,6 +6,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react';
import { useForm } from '../../../hooks';
import { focusEnd } from '../../../utils/element-helpers';
import styles from './CommentEdit.module.scss';
@@ -70,7 +71,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
useEffect(() => {
if (isOpened) {
textField.current.ref.current.focus();
focusEnd(textField.current.ref.current);
}
}, [isOpened]);
@@ -4,6 +4,7 @@ import classNames from 'classnames';
import { useTranslation, Trans } from 'react-i18next';
import { Comment } from 'semantic-ui-react';
import getDateFormat from '../../../utils/get-date-format';
import { ActivityTypes } from '../../../constants/Enums';
import ItemComment from './ItemComment';
import User from '../../User';
@@ -66,7 +67,7 @@ const Item = React.memo(({ type, data, createdAt, user }) => {
<div className={classNames(styles.content)}>
<div>{contentNode}</div>
<span className={styles.date}>
{t('format:longDateTime', {
{t(`format:${getDateFormat(createdAt)}`, {
postProcess: 'formatDate',
value: createdAt,
})}
@@ -6,6 +6,7 @@ import { Comment } from 'semantic-ui-react';
import { usePopup } from '../../../lib/popup';
import { Markdown } from '../../../lib/custom-ui';
import getDateFormat from '../../../utils/get-date-format';
import CommentEdit from './CommentEdit';
import User from '../../User';
import DeleteStep from '../../DeleteStep';
@@ -33,7 +34,7 @@ const ItemComment = React.memo(
<div className={styles.title}>
<span className={styles.author}>{user.name}</span>
<span className={styles.date}>
{t('format:longDateTime', {
{t(`format:${getDateFormat(createdAt)}`, {
postProcess: 'formatDate',
value: createdAt,
})}
@@ -94,7 +94,7 @@ const AttachmentAddZone = React.memo(({ children, onCreate }) => {
return (
<>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<div {...getRootProps()} className={styles.wrapper}>
<div {...getRootProps()}>
{isDragActive && <div className={styles.dropzone}>{t('common.dropFileToUpload')}</div>}
{children}
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
@@ -2,7 +2,7 @@
.dropzone {
background: white;
font-size: 20px;
font-weight: 700;
font-weight: bold;
height: 100%;
line-height: 30px;
opacity: 0.7;
@@ -12,8 +12,4 @@
width: 100%;
z-index: 2001;
}
.wrapper {
overflow: hidden;
}
}
@@ -16,7 +16,7 @@
.contentError {
color: #fff;
font-size: 20px;
font-weight: 700;
font-weight: bold;
height: 20px;
width: 470px;
}
@@ -35,7 +35,7 @@
color: #5e6c84;
display: block;
font-size: 18px;
font-weight: 700;
font-weight: bold;
height: 100%;
line-height: 80px;
overflow: hidden;
@@ -50,7 +50,7 @@
.name {
color: #17394d;
font-size: 14px;
font-weight: 700;
font-weight: bold;
line-height: 20px;
word-wrap: break-word;
}
+60 -7
View File
@@ -1,8 +1,8 @@
import React, { useCallback, useRef } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Button, Grid, Icon, Modal } from 'semantic-ui-react';
import { Button, Checkbox, Grid, Icon, Modal } from 'semantic-ui-react';
import { usePopup } from '../../lib/popup';
import { Markdown } from '../../lib/custom-ui';
@@ -32,6 +32,7 @@ const CardModal = React.memo(
name,
description,
dueDate,
isDueDateCompleted,
stopwatch,
isSubscribed,
isActivitiesFetching,
@@ -55,6 +56,7 @@ const CardModal = React.memo(
onUpdate,
onMove,
onTransfer,
onDuplicate,
onDelete,
onUserAdd,
onUserRemove,
@@ -80,6 +82,7 @@ const CardModal = React.memo(
onClose,
}) => {
const [t] = useTranslation();
const [isLinkCopied, setIsLinkCopied] = useState(false);
const isGalleryOpened = useRef(false);
@@ -116,6 +119,12 @@ const CardModal = React.memo(
[onUpdate],
);
const handleDueDateCompletionChange = useCallback(() => {
onUpdate({
isDueDateCompleted: !isDueDateCompleted,
});
}, [isDueDateCompleted, onUpdate]);
const handleStopwatchUpdate = useCallback(
(newStopwatch) => {
onUpdate({
@@ -140,6 +149,19 @@ const CardModal = React.memo(
});
}, [isSubscribed, onUpdate]);
const handleDuplicateClick = useCallback(() => {
onDuplicate();
onClose();
}, [onDuplicate, onClose]);
const handleCopyLinkClick = useCallback(() => {
navigator.clipboard.writeText(window.location.href);
setIsLinkCopied(true);
setTimeout(() => {
setIsLinkCopied(false);
}, 5000);
}, []);
const handleGalleryOpen = useCallback(() => {
isGalleryOpened.current = true;
}, []);
@@ -285,13 +307,24 @@ const CardModal = React.memo(
context: 'title',
})}
</div>
<span className={styles.attachment}>
<span className={classNames(styles.attachment, styles.attachmentDueDate)}>
{canEdit ? (
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<DueDate value={dueDate} />
</DueDateEditPopup>
<>
<Checkbox
checked={isDueDateCompleted}
disabled={!canEdit}
onChange={handleDueDateCompletionChange}
/>
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<DueDate
withStatusIcon
value={dueDate}
isCompleted={isDueDateCompleted}
/>
</DueDateEditPopup>
</>
) : (
<DueDate value={dueDate} />
<DueDate withStatusIcon value={dueDate} isCompleted={isDueDateCompleted} />
)}
</span>
</div>
@@ -496,6 +529,23 @@ const CardModal = React.memo(
{t('action.move')}
</Button>
</CardMovePopup>
<Button fluid className={styles.actionButton} onClick={handleDuplicateClick}>
<Icon name="copy outline" className={styles.actionIcon} />
{t('action.duplicate')}
</Button>
{window.isSecureContext && (
<Button fluid className={styles.actionButton} onClick={handleCopyLinkClick}>
<Icon
name={isLinkCopied ? 'linkify' : 'unlink'}
className={styles.actionIcon}
/>
{isLinkCopied
? t('common.linkIsCopied')
: t('action.copyLink', {
context: 'title',
})}
</Button>
)}
<DeletePopup
title="common.deleteCard"
content="common.areYouSureYouWantToDeleteThisCard"
@@ -530,6 +580,7 @@ CardModal.propTypes = {
name: PropTypes.string.isRequired,
description: PropTypes.string,
dueDate: PropTypes.instanceOf(Date),
isDueDateCompleted: PropTypes.bool,
stopwatch: PropTypes.object, // eslint-disable-line react/forbid-prop-types
isSubscribed: PropTypes.bool.isRequired,
isActivitiesFetching: PropTypes.bool.isRequired,
@@ -555,6 +606,7 @@ CardModal.propTypes = {
onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired,
onDuplicate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onUserAdd: PropTypes.func.isRequired,
onUserRemove: PropTypes.func.isRequired,
@@ -583,6 +635,7 @@ CardModal.propTypes = {
CardModal.defaultProps = {
description: undefined,
dueDate: undefined,
isDueDateCompleted: false,
stopwatch: undefined,
};
@@ -20,18 +20,26 @@
.actionIcon {
color: #17394d;
display: inline;
margin-right: 8px;
}
.actions {
margin-bottom: 24px;
@media only screen and (max-width: 797px) {
flex: 1;
}
@media only screen and (max-width: 425px) {
padding: 0;
width: 100%;
}
}
.actionsTitle {
color: #8c8c8c;
font-size: 12px;
font-weight: 500;
font-weight: normal;
letter-spacing: 0.04em;
margin-top: 16px;
text-transform: uppercase;
@@ -50,6 +58,12 @@
max-width: 100%;
}
.attachmentDueDate {
align-items: center;
display: flex;
gap: 4px;
}
.attachments {
display: inline-block;
margin: 0 8px 8px 0;
@@ -63,6 +77,11 @@
.contentPadding {
padding: 8px 8px 0 16px;
@media only screen and (max-width: 797px) {
padding-right: 16px;
width: 100% !important;
}
}
.cursorPointer {
@@ -139,7 +158,7 @@
.headerTitle {
color: #17394d;
font-size: 20px;
font-weight: 700;
font-weight: bold;
line-height: 24px;
}
@@ -155,6 +174,11 @@
.modalPadding {
padding: 0px;
@media only screen and (max-width: 797px) {
display: flex;
flex-flow: column nowrap;
}
}
.moduleHeader {
@@ -185,6 +209,15 @@
.sidebarPadding {
padding: 8px 16px 0 8px;
@media only screen and (max-width: 797px) {
align-items: flex-start;
display: flex;
flex-flow: row wrap;
gap: 10px;
padding-left: 16px;
width: 100% !important;
}
}
.text {
@@ -57,6 +57,7 @@ const DescriptionEdit = React.forwardRef(({ children, defaultValue, onUpdate },
const mdEditorOptions = useMemo(
() => ({
autoDownloadFontAwesome: false,
autofocus: true,
spellChecker: false,
status: false,
@@ -6,7 +6,7 @@
box-shadow: none;
color: #17394d;
font-size: 20px;
font-weight: 700;
font-weight: bold;
line-height: 24px;
margin: -5px;
overflow: hidden;
@@ -8,6 +8,7 @@ import { usePopup } from '../../../lib/popup';
import NameEdit from './NameEdit';
import ActionsStep from './ActionsStep';
import Linkify from '../../Linkify';
import styles from './Item.module.scss';
@@ -65,7 +66,7 @@ const Item = React.memo(
onClick={handleClick}
>
<span className={classNames(styles.task, isCompleted && styles.taskCompleted)}>
{name}
<Linkify linkStopPropagation>{name}</Linkify>
</span>
</span>
{isPersisted && canEdit && (
@@ -5,6 +5,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { Button, Form, TextArea } from 'semantic-ui-react';
import { useField } from '../../../hooks';
import { focusEnd } from '../../../utils/element-helpers';
import styles from './NameEdit.module.scss';
@@ -65,7 +66,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => {
if (isOpened) {
field.current.ref.current.focus();
focusEnd(field.current.ref.current);
}
}, [isOpened]);
@@ -4,6 +4,7 @@
border-radius: 4px;
bottom: 20px;
box-shadow: #b04632 0 1px 0;
max-width: calc(100% - 40px);
padding: 12px 18px;
position: fixed;
right: 20px;
+97 -4
View File
@@ -1,8 +1,12 @@
import upperFirst from 'lodash/upperFirst';
import React from 'react';
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Icon } from 'semantic-ui-react';
import { useForceUpdate } from '../../lib/hooks';
import getDateFormat from '../../utils/get-date-format';
import styles from './DueDate.module.scss';
@@ -12,27 +16,111 @@ const SIZES = {
MEDIUM: 'medium',
};
const FORMATS = {
const STATUSES = {
DUE_SOON: 'dueSoon',
OVERDUE: 'overdue',
COMPLETED: 'completed',
};
const LONG_DATE_FORMAT_BY_SIZE = {
tiny: 'longDate',
small: 'longDate',
medium: 'longDateTime',
};
const DueDate = React.memo(({ value, size, isDisabled, onClick }) => {
const FULL_DATE_FORMAT_BY_SIZE = {
tiny: 'fullDate',
small: 'fullDate',
medium: 'fullDateTime',
};
const STATUS_ICON_PROPS_BY_STATUS = {
[STATUSES.DUE_SOON]: {
name: 'hourglass half',
color: 'orange',
},
[STATUSES.OVERDUE]: {
name: 'hourglass end',
color: 'red',
},
[STATUSES.COMPLETED]: {
name: 'checkmark',
color: 'green',
},
};
const getStatus = (dateTime, isCompleted) => {
if (isCompleted) {
return STATUSES.COMPLETED;
}
const secondsLeft = Math.floor((dateTime.getTime() - new Date().getTime()) / 1000);
if (secondsLeft <= 0) {
return STATUSES.OVERDUE;
}
if (secondsLeft <= 24 * 60 * 60) {
return STATUSES.DUE_SOON;
}
return null;
};
const DueDate = React.memo(({ value, size, isCompleted, isDisabled, withStatusIcon, onClick }) => {
const [t] = useTranslation();
const forceUpdate = useForceUpdate();
const statusRef = useRef(null);
statusRef.current = getStatus(value, isCompleted);
const intervalRef = useRef(null);
const dateFormat = getDateFormat(
value,
LONG_DATE_FORMAT_BY_SIZE[size],
FULL_DATE_FORMAT_BY_SIZE[size],
);
useEffect(() => {
if ([null, STATUSES.DUE_SOON].includes(statusRef.current)) {
intervalRef.current = setInterval(() => {
const status = getStatus(value, isCompleted);
if (status !== statusRef.current) {
forceUpdate();
}
if (status === STATUSES.OVERDUE) {
clearInterval(intervalRef.current);
}
}, 1000);
}
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [value, isCompleted, forceUpdate]);
const contentNode = (
<span
className={classNames(
styles.wrapper,
styles[`wrapper${upperFirst(size)}`],
!withStatusIcon && statusRef.current && styles[`wrapper${upperFirst(statusRef.current)}`],
onClick && styles.wrapperHoverable,
)}
>
{t(`format:${FORMATS[size]}`, {
{t(`format:${dateFormat}`, {
value,
postProcess: 'formatDate',
})}
{withStatusIcon && statusRef.current && (
// eslint-disable-next-line react/jsx-props-no-spreading
<Icon {...STATUS_ICON_PROPS_BY_STATUS[statusRef.current]} className={styles.statusIcon} />
)}
</span>
);
@@ -48,14 +136,19 @@ const DueDate = React.memo(({ value, size, isDisabled, onClick }) => {
DueDate.propTypes = {
value: PropTypes.instanceOf(Date).isRequired,
size: PropTypes.oneOf(Object.values(SIZES)),
isCompleted: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool,
withStatusIcon: PropTypes.bool,
onClick: PropTypes.func,
onCompletionToggle: PropTypes.func,
};
DueDate.defaultProps = {
size: SIZES.MEDIUM,
isDisabled: false,
withStatusIcon: false,
onClick: undefined,
onCompletionToggle: undefined,
};
export default DueDate;
@@ -8,16 +8,17 @@
padding: 0;
}
.statusIcon {
line-height: 1;
margin: 0 0 0 8px;
}
.wrapper {
background: #dce0e4;
border: none;
border-radius: 3px;
color: #6a808b;
display: inline-block;
outline: none;
text-align: left;
transition: background 0.3s ease;
vertical-align: top;
}
.wrapperHoverable:hover {
@@ -43,4 +44,21 @@
line-height: 20px;
padding: 6px 12px;
}
/* Statuses */
.wrapperDueSoon {
background: #f2711c;
color: #fff;
}
.wrapperOverdue {
background: #db2828;
color: #fff;
}
.wrapperCompleted {
background: #21ba45;
color: #fff;
}
}
@@ -1,5 +1,6 @@
:global(#app) {
.wrapper {
max-width: 100vw;
position: fixed;
width: 100%;
z-index: 1;
+3 -1
View File
@@ -7,6 +7,7 @@ import { usePopup } from '../../lib/popup';
import Paths from '../../constants/Paths';
import NotificationsStep from './NotificationsStep';
import User from '../User';
import UserStep from '../UserStep';
import styles from './Header.module.scss';
@@ -91,7 +92,8 @@ const Header = React.memo(
onLogout={onLogout}
>
<Menu.Item className={classNames(styles.item, styles.itemHoverable)}>
{user.name}
<span className={styles.userName}>{user.name}</span>
<User name={user.name} avatarUrl={user.avatarUrl} size="small" />
</Menu.Item>
</UserPopup>
</Menu.Menu>
@@ -86,6 +86,15 @@
font-weight: bold;
}
.userName {
display: none;
margin-right: 10px;
@media only screen and (min-width: 797px) {
display: block;
}
}
.wrapper {
background: rgba(0, 0, 0, 0.24);
display: flex;
@@ -49,7 +49,6 @@
max-height: 60vh;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 5px;
+2 -2
View File
@@ -17,7 +17,7 @@ const SIZES = {
const Label = React.memo(({ name, color, size, isDisabled, onClick }) => {
const contentNode = (
<div
<span
title={name}
className={classNames(
styles.wrapper,
@@ -28,7 +28,7 @@ const Label = React.memo(({ name, color, size, isDisabled, onClick }) => {
)}
>
{name || '\u00A0'}
</div>
</span>
);
return onClick ? (
@@ -11,17 +11,17 @@
.wrapper {
border-radius: 3px;
box-sizing: border-box;
color: #fff;
font-weight: 400;
outline: none;
display: inline-block;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);
vertical-align: top;
white-space: nowrap;
}
.wrapperNameless{
.wrapperNameless {
width: 40px;
}
@@ -25,7 +25,6 @@
max-height: 60vh;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 5px;
+68
View File
@@ -0,0 +1,68 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import LinkifyReact from 'linkify-react';
import history from '../history';
const Linkify = React.memo(({ children, linkStopPropagation, ...props }) => {
const handleLinkClick = useCallback(
(event) => {
if (linkStopPropagation) {
event.stopPropagation();
}
if (!event.target.getAttribute('target')) {
event.preventDefault();
history.push(event.target.href);
}
},
[linkStopPropagation],
);
const linkRenderer = useCallback(
({ attributes: { href, ...linkProps }, content }) => {
let url;
try {
url = new URL(href, window.location);
} catch (error) {} // eslint-disable-line no-empty
const isSameSite = !!url && url.origin === window.location.origin;
return (
<a
{...linkProps} // eslint-disable-line react/jsx-props-no-spreading
href={href}
target={isSameSite ? undefined : '_blank'}
rel={isSameSite ? undefined : 'noreferrer'}
onClick={handleLinkClick}
>
{isSameSite ? url.pathname : content}
</a>
);
},
[handleLinkClick],
);
return (
<LinkifyReact
{...props} // eslint-disable-line react/jsx-props-no-spreading
options={{
defaultProtocol: 'https',
render: linkRenderer,
}}
>
{children}
</LinkifyReact>
);
});
Linkify.propTypes = {
children: PropTypes.string.isRequired,
linkStopPropagation: PropTypes.bool,
};
Linkify.defaultProps = {
linkStopPropagation: false,
};
export default Linkify;
+40 -11
View File
@@ -5,15 +5,17 @@ import { Menu } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui';
import { useSteps } from '../../hooks';
import ListSortStep from '../ListSortStep';
import DeleteStep from '../DeleteStep';
import styles from './ActionsStep.module.scss';
const StepTypes = {
DELETE: 'DELETE',
SORT: 'SORT',
};
const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) => {
const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onSort, onDelete, onClose }) => {
const [t] = useTranslation();
const [step, openStep, handleBack] = useSteps();
@@ -27,20 +29,41 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
onClose();
}, [onCardAdd, onClose]);
const handleSortClick = useCallback(() => {
openStep(StepTypes.SORT);
}, [openStep]);
const handleDeleteClick = useCallback(() => {
openStep(StepTypes.DELETE);
}, [openStep]);
if (step && step.type === StepTypes.DELETE) {
return (
<DeleteStep
title="common.deleteList"
content="common.areYouSureYouWantToDeleteThisList"
buttonContent="action.deleteList"
onConfirm={onDelete}
onBack={handleBack}
/>
);
const handleSortTypeSelect = useCallback(
(type) => {
onSort({
type,
});
onClose();
},
[onSort, onClose],
);
if (step && step.type) {
switch (step.type) {
case StepTypes.SORT:
return <ListSortStep onTypeSelect={handleSortTypeSelect} onBack={handleBack} />;
case StepTypes.DELETE:
return (
<DeleteStep
title="common.deleteList"
content="common.areYouSureYouWantToDeleteThisList"
buttonContent="action.deleteList"
onConfirm={onDelete}
onBack={handleBack}
/>
);
default:
}
}
return (
@@ -62,6 +85,11 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleSortClick}>
{t('action.sortList', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
{t('action.deleteList', {
context: 'title',
@@ -77,6 +105,7 @@ ActionsStep.propTypes = {
onNameEdit: PropTypes.func.isRequired,
onCardAdd: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onSort: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
+14 -1
View File
@@ -16,7 +16,18 @@ import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-ic
import styles from './List.module.scss';
const List = React.memo(
({ id, index, name, isPersisted, cardIds, canEdit, onUpdate, onDelete, onCardCreate }) => {
({
id,
index,
name,
isPersisted,
cardIds,
canEdit,
onUpdate,
onDelete,
onSort,
onCardCreate,
}) => {
const [t] = useTranslation();
const [isAddCardOpened, setIsAddCardOpened] = useState(false);
@@ -114,6 +125,7 @@ const List = React.memo(
onNameEdit={handleNameEdit}
onCardAdd={handleCardAdd}
onDelete={onDelete}
onSort={onSort}
>
<Button className={classNames(styles.headerButton, styles.target)}>
<Icon fitted name="pencil" size="small" />
@@ -159,6 +171,7 @@ List.propTypes = {
cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
canEdit: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired,
onSort: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onCardCreate: PropTypes.func.isRequired,
};
+15 -1
View File
@@ -43,7 +43,6 @@
max-height: calc(100vh - 268px);
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: thin;
width: 290px;
&:hover {
@@ -81,6 +80,12 @@
&:hover .target {
opacity: 1;
}
@media only screen and (max-width: 797px) {
.target {
opacity: 1;
}
}
}
.headerEditable {
@@ -104,6 +109,15 @@
background: rgba(9, 30, 66, 0.13);
color: #516b7a;
}
@media only screen and (max-width: 797px) {
&:focus,
&:active {
background: rgba(9, 30, 66, 0.13);
color: #516b7a;
}
}
}
.headerName {
+2 -1
View File
@@ -4,6 +4,7 @@ import TextareaAutosize from 'react-textarea-autosize';
import { TextArea } from 'semantic-ui-react';
import { useField } from '../../hooks';
import { focusEnd } from '../../utils/element-helpers';
import styles from './NameEdit.module.scss';
@@ -71,7 +72,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
useEffect(() => {
if (isOpened) {
field.current.ref.current.select();
focusEnd(field.current.ref.current);
}
}, [isOpened]);
@@ -0,0 +1,61 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Menu } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui';
import { ListSortTypes } from '../../constants/Enums';
import styles from './ListSortStep.module.scss';
const ListSortStep = React.memo(({ onTypeSelect, onBack }) => {
const [t] = useTranslation();
return (
<>
<Popup.Header onBack={onBack}>
{t('common.sortList', {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<Menu secondary vertical className={styles.menu}>
<Menu.Item
className={styles.menuItem}
onClick={() => onTypeSelect(ListSortTypes.NAME_ASC)}
>
{t('common.title')}
</Menu.Item>
<Menu.Item
className={styles.menuItem}
onClick={() => onTypeSelect(ListSortTypes.DUE_DATE_ASC)}
>
{t('common.dueDate')}
</Menu.Item>
<Menu.Item
className={styles.menuItem}
onClick={() => onTypeSelect(ListSortTypes.CREATED_AT_ASC)}
>
{t('common.oldestFirst')}
</Menu.Item>
<Menu.Item
className={styles.menuItem}
onClick={() => onTypeSelect(ListSortTypes.CREATED_AT_DESC)}
>
{t('common.newestFirst')}
</Menu.Item>
</Menu>
</Popup.Content>
</>
);
});
ListSortStep.propTypes = {
onTypeSelect: PropTypes.func.isRequired,
onBack: PropTypes.func,
};
ListSortStep.defaultProps = {
onBack: undefined,
};
export default ListSortStep;
@@ -0,0 +1,11 @@
:global(#app) {
.menu {
margin: -7px -12px -5px;
width: calc(100% + 24px);
}
.menuItem {
margin: 0;
padding-left: 14px;
}
}
@@ -0,0 +1,3 @@
import ListSortStep from './ListSortStep';
export default ListSortStep;
+56 -40
View File
@@ -18,6 +18,11 @@ const createMessage = (error) => {
}
switch (error.message) {
case 'Invalid credentials':
return {
type: 'error',
content: 'common.invalidCredentials',
};
case 'Invalid email or username':
return {
type: 'error',
@@ -68,6 +73,7 @@ const Login = React.memo(
isSubmittingUsingOidc,
error,
withOidc,
isOidcEnforced,
onAuthenticate,
onAuthenticateUsingOidc,
onMessageDismiss,
@@ -107,12 +113,15 @@ const Login = React.memo(
}, [onAuthenticate, data]);
useEffect(() => {
emailOrUsernameField.current.focus();
}, []);
if (!isOidcEnforced) {
emailOrUsernameField.current.focus();
}
}, [isOidcEnforced]);
useEffect(() => {
if (wasSubmitting && !isSubmitting && error) {
switch (error.message) {
case 'Invalid credentials':
case 'Invalid email or username':
emailOrUsernameField.current.select();
@@ -159,51 +168,57 @@ const Login = React.memo(
onDismiss={onMessageDismiss}
/>
)}
<Form size="large" onSubmit={handleSubmit}>
<div className={styles.inputWrapper}>
<div className={styles.inputLabel}>{t('common.emailOrUsername')}</div>
<Input
fluid
ref={emailOrUsernameField}
name="emailOrUsername"
value={data.emailOrUsername}
readOnly={isSubmitting}
className={styles.input}
onChange={handleFieldChange}
{!isOidcEnforced && (
<Form size="large" onSubmit={handleSubmit}>
<div className={styles.inputWrapper}>
<div className={styles.inputLabel}>{t('common.emailOrUsername')}</div>
<Input
fluid
ref={emailOrUsernameField}
name="emailOrUsername"
value={data.emailOrUsername}
readOnly={isSubmitting}
className={styles.input}
onChange={handleFieldChange}
/>
</div>
<div className={styles.inputWrapper}>
<div className={styles.inputLabel}>{t('common.password')}</div>
<Input.Password
fluid
ref={passwordField}
name="password"
value={data.password}
readOnly={isSubmitting}
className={styles.input}
onChange={handleFieldChange}
/>
</div>
<Form.Button
primary
size="large"
icon="right arrow"
labelPosition="right"
content={t('action.logIn')}
floated="right"
loading={isSubmitting}
disabled={isSubmitting || isSubmittingUsingOidc}
/>
</div>
<div className={styles.inputWrapper}>
<div className={styles.inputLabel}>{t('common.password')}</div>
<Input.Password
fluid
ref={passwordField}
name="password"
value={data.password}
readOnly={isSubmitting}
className={styles.input}
onChange={handleFieldChange}
/>
</div>
<Form.Button
primary
size="large"
icon="right arrow"
labelPosition="right"
content={t('action.logIn')}
floated="right"
loading={isSubmitting}
disabled={isSubmitting || isSubmittingUsingOidc}
/>
</Form>
</Form>
)}
{withOidc && (
<Button
type="button"
fluid={isOidcEnforced}
primary={isOidcEnforced}
size={isOidcEnforced ? 'large' : undefined}
icon={isOidcEnforced ? 'right arrow' : undefined}
labelPosition={isOidcEnforced ? 'right' : undefined}
content={t('action.logInWithSSO')}
loading={isSubmittingUsingOidc}
disabled={isSubmitting || isSubmittingUsingOidc}
onClick={onAuthenticateUsingOidc}
>
{t('action.logInWithSSO')}
</Button>
/>
)}
</div>
</div>
@@ -242,6 +257,7 @@ Login.propTypes = {
isSubmittingUsingOidc: PropTypes.bool.isRequired,
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
withOidc: PropTypes.bool.isRequired,
isOidcEnforced: PropTypes.bool.isRequired,
onAuthenticate: PropTypes.func.isRequired,
onAuthenticateUsingOidc: PropTypes.func.isRequired,
onMessageDismiss: PropTypes.func.isRequired,
@@ -3,6 +3,7 @@ import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui';
import { useSteps } from '../../hooks';
import User from '../User';
@@ -19,6 +20,7 @@ const ActionsStep = React.memo(
({
membership,
permissionsSelectStep,
title,
leaveButtonContent,
leaveConfirmationTitle,
leaveConfirmationContent,
@@ -31,6 +33,7 @@ const ActionsStep = React.memo(
canLeave,
onUpdate,
onDelete,
onBack,
onClose,
}) => {
const [t] = useTranslation();
@@ -53,6 +56,11 @@ const ActionsStep = React.memo(
[onUpdate],
);
const handleDeleteConfirm = useCallback(() => {
onDelete();
onClose();
}, [onDelete, onClose]);
if (step) {
switch (step.type) {
case StepTypes.EDIT_PERMISSIONS: {
@@ -81,7 +89,7 @@ const ActionsStep = React.memo(
? leaveConfirmationButtonContent
: deleteConfirmationButtonContent
}
onConfirm={onDelete}
onConfirm={handleDeleteConfirm}
onBack={handleBack}
/>
);
@@ -89,7 +97,7 @@ const ActionsStep = React.memo(
}
}
return (
const contentNode = (
<>
<span className={styles.user}>
<User name={membership.user.name} avatarUrl={membership.user.avatarUrl} size="large" />
@@ -125,12 +133,26 @@ const ActionsStep = React.memo(
)}
</>
);
return onBack ? (
<>
<Popup.Header onBack={onBack}>
{t(title, {
context: 'title',
})}
</Popup.Header>
<Popup.Content>{contentNode}</Popup.Content>
</>
) : (
contentNode
);
},
);
ActionsStep.propTypes = {
membership: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
permissionsSelectStep: PropTypes.elementType,
title: PropTypes.string,
leaveButtonContent: PropTypes.string,
leaveConfirmationTitle: PropTypes.string,
leaveConfirmationContent: PropTypes.string,
@@ -143,11 +165,13 @@ ActionsStep.propTypes = {
canLeave: PropTypes.bool.isRequired,
onUpdate: PropTypes.func,
onDelete: PropTypes.func.isRequired,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired,
};
ActionsStep.defaultProps = {
permissionsSelectStep: undefined,
title: 'common.memberActions',
leaveButtonContent: 'action.leaveBoard',
leaveConfirmationTitle: 'common.leaveBoard',
leaveConfirmationContent: 'common.areYouSureYouWantToLeaveBoard',
@@ -157,6 +181,7 @@ ActionsStep.defaultProps = {
deleteConfirmationContent: 'common.areYouSureYouWantToRemoveThisMemberFromBoard',
deleteConfirmationButtonContent: 'action.removeMember',
onUpdate: undefined,
onBack: undefined,
};
export default ActionsStep;
@@ -4,7 +4,6 @@
max-height: 60vh;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 5px;
@@ -5,16 +5,21 @@ import { usePopup } from '../../lib/popup';
import AddStep from './AddStep';
import ActionsStep from './ActionsStep';
import MembershipsStep from './MembershipsStep';
import User from '../User';
import styles from './Memberships.module.scss';
const MAX_MEMBERS = 6;
const Memberships = React.memo(
({
items,
allUsers,
permissionsSelectStep,
title,
addTitle,
actionsTitle,
leaveButtonContent,
leaveConfirmationTitle,
leaveConfirmationContent,
@@ -31,11 +36,14 @@ const Memberships = React.memo(
}) => {
const AddPopup = usePopup(AddStep);
const ActionsPopup = usePopup(ActionsStep);
const MembershipsPopup = usePopup(MembershipsStep);
const remainMembersCount = items.length - MAX_MEMBERS;
return (
<>
<span className={styles.users}>
{items.map((item) => (
{items.slice(0, MAX_MEMBERS).map((item) => (
<span key={item.id} className={styles.user}>
<ActionsPopup
membership={item}
@@ -63,6 +71,30 @@ const Memberships = React.memo(
</span>
))}
</span>
{remainMembersCount > 0 && (
<MembershipsPopup
items={items}
permissionsSelectStep={permissionsSelectStep}
title={title}
actionsTitle={actionsTitle}
leaveButtonContent={leaveButtonContent}
leaveConfirmationTitle={leaveConfirmationTitle}
leaveConfirmationContent={leaveConfirmationContent}
leaveConfirmationButtonContent={leaveConfirmationButtonContent}
deleteButtonContent={deleteButtonContent}
deleteConfirmationTitle={deleteConfirmationTitle}
deleteConfirmationContent={deleteConfirmationContent}
deleteConfirmationButtonContent={deleteConfirmationButtonContent}
canEdit={canEdit}
canLeave={items.length > 1 || canLeaveIfLast}
onUpdate={onUpdate}
onDelete={onDelete}
>
<Button icon className={styles.addUser}>
+{remainMembersCount < 99 ? remainMembersCount : 99}
</Button>
</MembershipsPopup>
)}
{canEdit && (
<AddPopup
users={allUsers}
@@ -85,7 +117,9 @@ Memberships.propTypes = {
allUsers: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
permissionsSelectStep: PropTypes.elementType,
title: PropTypes.string,
addTitle: PropTypes.string,
actionsTitle: PropTypes.string,
leaveButtonContent: PropTypes.string,
leaveConfirmationTitle: PropTypes.string,
leaveConfirmationContent: PropTypes.string,
@@ -103,7 +137,9 @@ Memberships.propTypes = {
Memberships.defaultProps = {
permissionsSelectStep: undefined,
title: undefined,
addTitle: undefined,
actionsTitle: undefined,
leaveButtonContent: undefined,
leaveConfirmationTitle: undefined,
leaveConfirmationContent: undefined,
@@ -11,6 +11,10 @@
vertical-align: top;
width: 36px;
@media only screen and (max-width: 797px) {
margin-left: 10px;
}
&:hover {
background: rgba(0, 0, 0, 0.32);
}
@@ -0,0 +1,121 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { useSteps } from '../../hooks';
import ActionsStep from './ActionsStep';
import BoardMembershipsStep from '../BoardMembershipsStep';
const StepTypes = {
EDIT: 'EDIT',
};
const MembershipsStep = React.memo(
({
items,
permissionsSelectStep,
title,
actionsTitle,
leaveButtonContent,
leaveConfirmationTitle,
leaveConfirmationContent,
leaveConfirmationButtonContent,
deleteButtonContent,
deleteConfirmationTitle,
deleteConfirmationContent,
deleteConfirmationButtonContent,
canEdit,
canLeave,
onUpdate,
onDelete,
onClose,
}) => {
const [step, openStep, handleBack] = useSteps();
const handleUserSelect = useCallback(
(userId) => {
openStep(StepTypes.EDIT, {
userId,
});
},
[openStep],
);
if (step && step.type === StepTypes.EDIT) {
const currentItem = items.find((item) => item.userId === step.params.userId);
if (currentItem) {
return (
<ActionsStep
membership={currentItem}
permissionsSelectStep={permissionsSelectStep}
title={actionsTitle}
leaveButtonContent={leaveButtonContent}
leaveConfirmationTitle={leaveConfirmationTitle}
leaveConfirmationContent={leaveConfirmationContent}
leaveConfirmationButtonContent={leaveConfirmationButtonContent}
deleteButtonContent={deleteButtonContent}
deleteConfirmationTitle={deleteConfirmationTitle}
deleteConfirmationContent={deleteConfirmationContent}
deleteConfirmationButtonContent={deleteConfirmationButtonContent}
canEdit={canEdit}
canLeave={canLeave}
onUpdate={(data) => onUpdate(currentItem.id, data)}
onDelete={() => onDelete(currentItem.id)}
onBack={handleBack}
onClose={onClose}
/>
);
}
openStep(null);
}
return (
// FIXME: hack
<BoardMembershipsStep
items={items}
currentUserIds={[]}
title={title}
onUserSelect={handleUserSelect}
onUserDeselect={() => {}}
/>
);
},
);
MembershipsStep.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
permissionsSelectStep: PropTypes.elementType,
title: PropTypes.string,
actionsTitle: PropTypes.string,
leaveButtonContent: PropTypes.string,
leaveConfirmationTitle: PropTypes.string,
leaveConfirmationContent: PropTypes.string,
leaveConfirmationButtonContent: PropTypes.string,
deleteButtonContent: PropTypes.string,
deleteConfirmationTitle: PropTypes.string,
deleteConfirmationContent: PropTypes.string,
deleteConfirmationButtonContent: PropTypes.string,
canEdit: PropTypes.bool.isRequired,
canLeave: PropTypes.bool.isRequired,
onUpdate: PropTypes.func,
onDelete: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
MembershipsStep.defaultProps = {
permissionsSelectStep: undefined,
title: undefined,
actionsTitle: undefined,
leaveButtonContent: undefined,
leaveConfirmationTitle: undefined,
leaveConfirmationContent: undefined,
leaveConfirmationButtonContent: undefined,
deleteButtonContent: undefined,
deleteConfirmationTitle: undefined,
deleteConfirmationContent: undefined,
deleteConfirmationButtonContent: undefined,
onUpdate: undefined,
};
export default MembershipsStep;
@@ -12,7 +12,9 @@ const ManagersPane = React.memo(({ items, allUsers, onCreate, onDelete }) => {
<Memberships
items={items}
allUsers={allUsers}
title="common.managers"
addTitle="common.addManager"
actionsTitle="common.managerActions"
leaveButtonContent="action.leaveProject"
leaveConfirmationTitle="common.leaveProject"
leaveConfirmationContent="common.areYouSureYouWantToLeaveProject"
+1
View File
@@ -13,6 +13,7 @@ import 'react-datepicker/dist/react-datepicker.css';
import 'photoswipe/dist/photoswipe.css';
import 'easymde/dist/easymde.min.css';
import '../lib/custom-ui/styles.css';
import '../assets/css/font-awesome.css';
import '../styles.module.scss';
function Root({ store, history }) {
@@ -10,13 +10,10 @@
.wrapper {
background: #dce0e4;
border: none;
border-radius: 3px;
color: #6a808b;
display: inline-block;
font-variant-numeric: tabular-nums;
outline: none;
text-align: left;
transition: background 0.3s ease;
vertical-align: top;
}
@@ -14,12 +14,10 @@
}
.wrapper {
border: none;
border-radius: 50%;
color: #fff;
display: inline-block;
line-height: 1;
outline: none;
text-align: center;
vertical-align: top;
}
@@ -32,7 +30,6 @@
.wrapperTiny {
font-size: 10px;
font-weight: 400;
height: 24px;
line-height: 20px;
padding: 2px 0;
@@ -41,7 +38,6 @@
.wrapperSmall {
font-size: 12px;
font-weight: 400;
height: 28px;
padding: 8px 0;
width: 28px;
@@ -49,7 +45,6 @@
.wrapperMedium {
font-size: 14px;
font-weight: 500;
height: 32px;
padding: 10px 0;
width: 32px;
@@ -57,7 +52,6 @@
.wrapperLarge {
font-size: 14px;
font-weight: 500;
height: 36px;
padding: 12px 0 10px;
width: 36px;
@@ -65,7 +59,6 @@
.wrapperMassive {
font-size: 36px;
font-weight: 500;
height: 100px;
padding: 32px 0 10px;
width: 100px;
@@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { Image, Tab } from 'semantic-ui-react';
import Config from '../../constants/Config';
import version from '../../version';
import logo from '../../assets/images/logo.png';
@@ -15,7 +15,7 @@ const AboutPane = React.memo(() => {
<Tab.Pane attached={false} className={styles.wrapper}>
<Image centered src={logo} size="large" />
<div className={styles.version}>
{t('common.version')} {Config.VERSION}
{t('common.version')} {version}
</div>
</Tab.Pane>
);
@@ -24,6 +24,7 @@ const AccountPane = React.memo(
organization,
language,
isLocked,
isUsernameLocked,
isAvatarUpdating,
usernameUpdateForm,
emailUpdateForm,
@@ -104,7 +105,7 @@ const AccountPane = React.memo(
value={language || 'auto'}
onChange={handleLanguageChange}
/>
{!isLocked && (
{(!isLocked || !isUsernameLocked) && (
<>
<Divider horizontal section>
<Header as="h4">
@@ -113,56 +114,62 @@ const AccountPane = React.memo(
})}
</Header>
</Divider>
<div className={styles.action}>
<UserUsernameEditPopup
usePasswordConfirmation
defaultData={usernameUpdateForm.data}
username={username}
isSubmitting={usernameUpdateForm.isSubmitting}
error={usernameUpdateForm.error}
onUpdate={onUsernameUpdate}
onMessageDismiss={onUsernameUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editUsername', {
context: 'title',
})}
</Button>
</UserUsernameEditPopup>
</div>
<div className={styles.action}>
<UserEmailEditPopup
usePasswordConfirmation
defaultData={emailUpdateForm.data}
email={email}
isSubmitting={emailUpdateForm.isSubmitting}
error={emailUpdateForm.error}
onUpdate={onEmailUpdate}
onMessageDismiss={onEmailUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editEmail', {
context: 'title',
})}
</Button>
</UserEmailEditPopup>
</div>
<div className={styles.action}>
<UserPasswordEditPopup
usePasswordConfirmation
defaultData={passwordUpdateForm.data}
isSubmitting={passwordUpdateForm.isSubmitting}
error={passwordUpdateForm.error}
onUpdate={onPasswordUpdate}
onMessageDismiss={onPasswordUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editPassword', {
context: 'title',
})}
</Button>
</UserPasswordEditPopup>
</div>
{!isUsernameLocked && (
<div className={styles.action}>
<UserUsernameEditPopup
defaultData={usernameUpdateForm.data}
username={username}
isSubmitting={usernameUpdateForm.isSubmitting}
error={usernameUpdateForm.error}
usePasswordConfirmation={!isLocked} // FIXME: hack
onUpdate={onUsernameUpdate}
onMessageDismiss={onUsernameUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editUsername', {
context: 'title',
})}
</Button>
</UserUsernameEditPopup>
</div>
)}
{!isLocked && (
<>
<div className={styles.action}>
<UserEmailEditPopup
usePasswordConfirmation
defaultData={emailUpdateForm.data}
email={email}
isSubmitting={emailUpdateForm.isSubmitting}
error={emailUpdateForm.error}
onUpdate={onEmailUpdate}
onMessageDismiss={onEmailUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editEmail', {
context: 'title',
})}
</Button>
</UserEmailEditPopup>
</div>
<div className={styles.action}>
<UserPasswordEditPopup
usePasswordConfirmation
defaultData={passwordUpdateForm.data}
isSubmitting={passwordUpdateForm.isSubmitting}
error={passwordUpdateForm.error}
onUpdate={onPasswordUpdate}
onMessageDismiss={onPasswordUpdateMessageDismiss}
>
<Button className={styles.actionButton}>
{t('action.editPassword', {
context: 'title',
})}
</Button>
</UserPasswordEditPopup>
</div>
</>
)}
</>
)}
</Tab.Pane>
@@ -179,6 +186,7 @@ AccountPane.propTypes = {
organization: PropTypes.string,
language: PropTypes.string,
isLocked: PropTypes.bool.isRequired,
isUsernameLocked: PropTypes.bool.isRequired,
isAvatarUpdating: PropTypes.bool.isRequired,
/* eslint-disable react/forbid-prop-types */
usernameUpdateForm: PropTypes.object.isRequired,
@@ -18,6 +18,7 @@ const UserSettingsModal = React.memo(
language,
subscribeToOwnCards,
isLocked,
isUsernameLocked,
isAvatarUpdating,
usernameUpdateForm,
emailUpdateForm,
@@ -50,6 +51,7 @@ const UserSettingsModal = React.memo(
organization={organization}
language={language}
isLocked={isLocked}
isUsernameLocked={isUsernameLocked}
isAvatarUpdating={isAvatarUpdating}
usernameUpdateForm={usernameUpdateForm}
emailUpdateForm={emailUpdateForm}
@@ -108,6 +110,7 @@ UserSettingsModal.propTypes = {
language: PropTypes.string,
subscribeToOwnCards: PropTypes.bool.isRequired,
isLocked: PropTypes.bool.isRequired,
isUsernameLocked: PropTypes.bool.isRequired,
isAvatarUpdating: PropTypes.bool.isRequired,
/* eslint-disable react/forbid-prop-types */
usernameUpdateForm: PropTypes.object.isRequired,
@@ -136,13 +136,15 @@ const ActionsStep = React.memo(
context: 'title',
})}
</Menu.Item>
{!user.isUsernameLocked && (
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
{t('action.editUsername', {
context: 'title',
})}
</Menu.Item>
)}
{!user.isLocked && (
<>
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
{t('action.editUsername', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}>
{t('action.editEmail', {
context: 'title',
@@ -19,6 +19,7 @@ const Item = React.memo(
isAdmin,
isLocked,
isRoleLocked,
isUsernameLocked,
isDeletionLocked,
emailUpdateForm,
passwordUpdateForm,
@@ -61,6 +62,7 @@ const Item = React.memo(
phone,
isAdmin,
isLocked,
isUsernameLocked,
isDeletionLocked,
emailUpdateForm,
passwordUpdateForm,
@@ -95,6 +97,7 @@ Item.propTypes = {
isAdmin: PropTypes.bool.isRequired,
isLocked: PropTypes.bool.isRequired,
isRoleLocked: PropTypes.bool.isRequired,
isUsernameLocked: PropTypes.bool.isRequired,
isDeletionLocked: PropTypes.bool.isRequired,
/* eslint-disable react/forbid-prop-types */
emailUpdateForm: PropTypes.object.isRequired,
@@ -10,6 +10,7 @@ import Item from './Item';
const UsersModal = React.memo(
({
items,
canAdd,
onUpdate,
onUsernameUpdate,
onUsernameUpdateMessageDismiss,
@@ -112,6 +113,7 @@ const UsersModal = React.memo(
isAdmin={item.isAdmin}
isLocked={item.isLocked}
isRoleLocked={item.isRoleLocked}
isUsernameLocked={item.isUsernameLocked}
isDeletionLocked={item.isDeletionLocked}
emailUpdateForm={item.emailUpdateForm}
passwordUpdateForm={item.passwordUpdateForm}
@@ -129,11 +131,13 @@ const UsersModal = React.memo(
</Table.Body>
</Table>
</Modal.Content>
<Modal.Actions>
<UserAddPopupContainer>
<Button positive content={t('action.addUser')} />
</UserAddPopupContainer>
</Modal.Actions>
{canAdd && (
<Modal.Actions>
<UserAddPopupContainer>
<Button positive content={t('action.addUser')} />
</UserAddPopupContainer>
</Modal.Actions>
)}
</Modal>
);
},
@@ -141,6 +145,7 @@ const UsersModal = React.memo(
UsersModal.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
canAdd: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired,
onUsernameUpdate: PropTypes.func.isRequired,
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
+8 -3
View File
@@ -173,6 +173,10 @@ export default {
LIST_UPDATE__SUCCESS: 'LIST_UPDATE__SUCCESS',
LIST_UPDATE__FAILURE: 'LIST_UPDATE__FAILURE',
LIST_UPDATE_HANDLE: 'LIST_UPDATE_HANDLE',
LIST_SORT: 'LIST_SORT',
LIST_SORT__SUCCESS: 'LIST_SORT__SUCCESS',
LIST_SORT__FAILURE: 'LIST_SORT__FAILURE',
LIST_SORT_HANDLE: 'LIST_SORT_HANDLE',
LIST_DELETE: 'LIST_DELETE',
LIST_DELETE__SUCCESS: 'LIST_DELETE__SUCCESS',
LIST_DELETE__FAILURE: 'LIST_DELETE__FAILURE',
@@ -191,13 +195,14 @@ export default {
CARD_UPDATE__SUCCESS: 'CARD_UPDATE__SUCCESS',
CARD_UPDATE__FAILURE: 'CARD_UPDATE__FAILURE',
CARD_UPDATE_HANDLE: 'CARD_UPDATE_HANDLE',
CARD_TRANSFER: 'CARD_TRANSFER',
CARD_TRANSFER__SUCCESS: 'CARD_TRANSFER__SUCCESS',
CARD_TRANSFER__FAILURE: 'CARD_TRANSFER__FAILURE',
CARD_DUPLICATE: 'CARD_DUPLICATE',
CARD_DUPLICATE__SUCCESS: 'CARD_DUPLICATE__SUCCESS',
CARD_DUPLICATE__FAILURE: 'CARD_DUPLICATE__FAILURE',
CARD_DELETE: 'CARD_DELETE',
CARD_DELETE__SUCCESS: 'CARD_DELETE__SUCCESS',
CARD_DELETE__FAILURE: 'CARD_DELETE__FAILURE',
CARD_DELETE_HANDLE: 'CARD_DELETE_HANDLE',
TEXT_FILTER_IN_CURRENT_BOARD: 'TEXT_FILTER_IN_CURRENT_BOARD',
/* Tasks */
+3 -3
View File
@@ -1,12 +1,12 @@
const VERSION = process.env.REACT_APP_VERSION;
const { BASE_URL } = window;
const BASE_PATH = BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
const SERVER_BASE_URL =
process.env.REACT_APP_SERVER_BASE_URL ||
(process.env.NODE_ENV === 'production' ? BASE_URL : 'http://localhost:1337');
const SERVER_BASE_PATH = SERVER_BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
const SERVER_HOST_NAME = SERVER_BASE_URL.replace(/^(.*\/\/[^/?#]*).*$/, '$1');
const ACCESS_TOKEN_KEY = 'accessToken';
@@ -17,9 +17,9 @@ const POSITION_GAP = 65535;
const ACTIVITIES_LIMIT = 50;
export default {
VERSION,
BASE_PATH,
SERVER_BASE_URL,
SERVER_BASE_PATH,
SERVER_HOST_NAME,
ACCESS_TOKEN_KEY,
ACCESS_TOKEN_VERSION_KEY,
+5
View File
@@ -120,6 +120,8 @@ export default {
LIST_MOVE: `${PREFIX}/LIST_MOVE`,
LIST_DELETE: `${PREFIX}/LIST_DELETE`,
LIST_DELETE_HANDLE: `${PREFIX}/LIST_DELETE_HANDLE`,
LIST_SORT: `${PREFIX}/LIST_SORT`,
LIST_SORT_HANDLE: `${PREFIX}/LIST_SORT_HANDLE`,
/* Cards */
@@ -132,9 +134,12 @@ export default {
CURRENT_CARD_MOVE: `${PREFIX}/CURRENT_CARD_MOVE`,
CARD_TRANSFER: `${PREFIX}/CARD_TRANSFER`,
CURRENT_CARD_TRANSFER: `${PREFIX}/CURRENT_CARD_TRANSFER`,
CARD_DUPLICATE: `${PREFIX}/CARD_DUPLICATE`,
CURRENT_CARD_DUPLICATE: `${PREFIX}/CURRENT_CARD_DUPLICATE`,
CARD_DELETE: `${PREFIX}/CARD_DELETE`,
CURRENT_CARD_DELETE: `${PREFIX}/CURRENT_CARD_DELETE`,
CARD_DELETE_HANDLE: `${PREFIX}/CARD_DELETE_HANDLE`,
TEXT_FILTER_IN_CURRENT_BOARD: `${PREFIX}/FILTER_TEXT_HANDLE`,
/* Tasks */
+7
View File
@@ -8,6 +8,13 @@ export const BoardMembershipRoles = {
VIEWER: 'viewer',
};
export const ListSortTypes = {
NAME_ASC: 'name_asc',
DUE_DATE_ASC: 'dueDate_asc',
CREATED_AT_ASC: 'createdAt_asc',
CREATED_AT_DESC: 'createdAt_desc',
};
export const ActivityTypes = {
CREATE_CARD: 'createCard',
MOVE_CARD: 'moveCard',
@@ -13,6 +13,7 @@ const mapStateToProps = (state) => {
const labels = selectors.selectLabelsForCurrentBoard(state);
const filterUsers = selectors.selectFilterUsersForCurrentBoard(state);
const filterLabels = selectors.selectFilterLabelsForCurrentBoard(state);
const filterText = selectors.selectFilterTextForCurrentBoard(state);
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
const isCurrentUserEditor =
@@ -23,6 +24,7 @@ const mapStateToProps = (state) => {
labels,
filterUsers,
filterLabels,
filterText,
allUsers,
canEdit: isCurrentUserEditor,
canEditMemberships: isCurrentUserManager,
@@ -43,6 +45,7 @@ const mapDispatchToProps = (dispatch) =>
onLabelUpdate: entryActions.updateLabel,
onLabelMove: entryActions.moveLabel,
onLabelDelete: entryActions.deleteLabel,
onTextFilterUpdate: entryActions.filterText,
},
dispatch,
);

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