Compare commits

...
100 Commits
Author SHA1 Message Date
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
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
152 changed files with 14843 additions and 14765 deletions
@@ -40,5 +40,5 @@ jobs:
build-args: ALPINE_VERSION=${{ env.ALPINE_VERSION }} build-args: ALPINE_VERSION=${{ env.ALPINE_VERSION }}
push: true push: true
tags: | tags: |
ghcr.io/plankanban/planka:base-latest ghcr.io/${{ github.repository }}:base-latest
ghcr.io/plankanban/planka:base-${{ env.ALPINE_VERSION }} ghcr.io/${{ github.repository }}:base-${{ env.ALPINE_VERSION }}
@@ -9,99 +9,45 @@ on:
- 'docker-*.sh' - 'docker-*.sh'
- '*.md' - '*.md'
branches: [master] branches: [master]
workflow_dispatch:
env: env:
REGISTRY_IMAGE: ghcr.io/plankanban/planka REGISTRY_IMAGE: ghcr.io/${{ github.repository }}
jobs: jobs:
build: build:
strategy: runs-on: self-hosted
fail-fast: false
matrix:
include:
- os: [self-hosted, x64]
platform: linux/amd64
- os: [self-hosted, arm64]
platform: linux/arm64
- os: [self-hosted, arm64]
platform: linux/arm/v7
runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v2
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest
id: build - name: Generate docker image tags
uses: docker/build-push-action@v5 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: with:
context: . context: .
platforms: ${{ matrix.platform }} platforms: linux/amd64,linux/arm64,linux/arm/v7
labels: ${{ steps.meta.outputs.labels }} push: true
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true tags: ${{ steps.metadata.outputs.tags }}
- name: Export digest labels: ${{ steps.metadata.outputs.labels }}
run: | cache-from: type=gha
mkdir -p /tmp/digests cache-to: type=gha,mode=max
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v3
with:
name: digests
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
runs-on: [self-hosted]
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v3
with:
name: digests
path: /tmp/digests
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
rerun-failed-jobs:
runs-on: [self-hosted]
needs: [ build, merge]
if: failure()
steps:
- name: Rerun failed jobs in the current workflow
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh run rerun ${{ github.run_id }} --failed
@@ -31,12 +31,23 @@ jobs:
result-encoding: string result-encoding: string
script: return context.payload.release.tag_name.replace('v', '') 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 - name: Build and push
uses: docker/build-push-action@v4 uses: docker/build-push-action@v4
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true push: true
tags: | tags: ${{ steps.metadata.outputs.tags }}
ghcr.io/plankanban/planka:latest labels: ${{ steps.metadata.outputs.labels }}
ghcr.io/plankanban/planka:${{ steps.set-version.outputs.result }} cache-from: type=gha
cache-to: type=gha,mode=max
+8 -3
View File
@@ -6,7 +6,7 @@ RUN apk -U upgrade \
WORKDIR /app WORKDIR /app
COPY server/package.json server/package-lock.json . COPY server/package.json server/package-lock.json ./
RUN npm install npm@latest --global \ RUN npm install npm@latest --global \
&& npm install pnpm --global \ && npm install pnpm --global \
@@ -17,7 +17,7 @@ FROM node:lts AS client
WORKDIR /app WORKDIR /app
COPY client/package.json client/package-lock.json . COPY client/package.json client/package-lock.json ./
RUN npm install npm@latest --global \ RUN npm install npm@latest --global \
&& npm install pnpm --global \ && npm install pnpm --global \
@@ -38,6 +38,7 @@ WORKDIR /app
COPY --chown=node:node start.sh . COPY --chown=node:node start.sh .
COPY --chown=node:node server . COPY --chown=node:node server .
COPY --chown=node:node healthcheck.js .
RUN mv .env.sample .env RUN mv .env.sample .env
@@ -52,4 +53,8 @@ VOLUME /app/private/attachments
EXPOSE 1337 EXPOSE 1337
CMD ["./start.sh"] HEALTHCHECK --interval=10s --timeout=2s --start-period=15s \
CMD node ./healthcheck.js
CMD [ "bash", "start.sh" ]
+10 -5
View File
@@ -10,13 +10,14 @@
## Features ## Features
- Create projects, boards, lists, cards, labels and tasks - Create projects, boards, lists, cards, labels and tasks
- Add card members, track time, set a due date, add attachments, write comments - Add card members, track time, set due dates, add attachments, write comments
- Markdown support in a card description and comment - Markdown support in card description and comments
- Filter by members and labels - Filter by members and labels
- Customize project background - Customize project backgrounds
- Real-time updates - Real-time updates
- User notifications - Internal notifications
- Internationalization - Multiple interface languages
- Single sign-on via OpenID Connect
## How to deploy Planka ## How to deploy Planka
@@ -44,3 +45,7 @@ See the [development section](https://docs.planka.cloud/docs/Development).
## License ## License
Planka is [AGPL-3.0 licensed](https://github.com/plankanban/planka/blob/master/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 # 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. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.15 version: 0.1.29
# This is the version number of the application being deployed. This version number should be # 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 # 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. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "1.15.4" appVersion: "1.17.2"
dependencies: dependencies:
- alias: postgresql - alias: postgresql
+3 -2
View File
@@ -46,7 +46,7 @@ helm install planka . --set secretkey=$SECRETKEY \
--set admin_email="demo@demo.demo" \ --set admin_email="demo@demo.demo" \
--set admin_password="demo" \ --set admin_password="demo" \
--set admin_name="Demo Demo" \ --set admin_name="Demo Demo" \
--set admin_username="demo" --set admin_username="demo" \
--set ingress.enabled=true \ --set ingress.enabled=true \
--set ingress.hosts[0].host=planka.example.dev \ --set ingress.hosts[0].host=planka.example.dev \
@@ -55,7 +55,7 @@ helm install planka . --set secretkey=$SECRETKEY \
--set admin_email="demo@demo.demo" \ --set admin_email="demo@demo.demo" \
--set admin_password="demo" \ --set admin_password="demo" \
--set admin_name="Demo Demo" \ --set admin_name="Demo Demo" \
--set admin_username="demo" --set admin_username="demo" \
--set ingress.enabled=true \ --set ingress.enabled=true \
--set ingress.hosts[0].host=planka.example.dev \ --set ingress.hosts[0].host=planka.example.dev \
--set ingress.tls[0].secretName=planka-tls \ --set ingress.tls[0].secretName=planka-tls \
@@ -76,6 +76,7 @@ admin_name: "Demo Demo"
admin_username: "demo" admin_username: "demo"
# Admin user # Admin user
# Ingress
ingress: ingress:
enabled: true enabled: true
hosts: hosts:
+14
View File
@@ -55,14 +55,24 @@ spec:
- mountPath: /app/private/attachments - mountPath: /app/private/attachments
subPath: attachments subPath: attachments
name: planka name: planka
{{- if .Values.securityContext.readOnlyRootFilesystem }}
- mountPath: /app/logs
subPath: app-logs
name: emptydir
{{- end }}
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
env: env:
{{- if not .Values.postgresql.enabled }}
- name: DATABASE_URL
value: {{ required "If the included postgresql deployment is disabled you need to define a Database URL in 'dburl'" .Values.dburl }}
{{- else }}
- name: DATABASE_URL - name: DATABASE_URL
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: planka-postgresql-svcbind-custom-user name: planka-postgresql-svcbind-custom-user
key: uri key: uri
{{- end }}
- name: BASE_URL - name: BASE_URL
{{- if .Values.baseUrl }} {{- if .Values.baseUrl }}
value: {{ .Values.baseUrl }} value: {{ .Values.baseUrl }}
@@ -134,3 +144,7 @@ spec:
{{- else }} {{- else }}
emptyDir: {} emptyDir: {}
{{- end }} {{- end }}
{{- if .Values.securityContext.readOnlyRootFilesystem }}
- name: emptydir
emptyDir: {}
{{- end }}
+7 -3
View File
@@ -1,5 +1,5 @@
{{- if .Values.autoscaling.enabled }} {{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1 apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler kind: HorizontalPodAutoscaler
metadata: metadata:
name: {{ include "planka.fullname" . }} name: {{ include "planka.fullname" . }}
@@ -17,12 +17,16 @@ spec:
- type: Resource - type: Resource
resource: resource:
name: cpu name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }} {{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource - type: Resource
resource: resource:
name: memory name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }} {{- end }}
{{- end }} {{- end }}
+3 -1
View File
@@ -105,12 +105,14 @@ postgresql:
serviceBindings: serviceBindings:
enabled: true enabled: true
## Set this if you disable the built-in postgresql deployment
dburl:
## PVC-based data storage configuration ## PVC-based data storage configuration
persistence: persistence:
enabled: false enabled: false
# existingClaim: netbox-data # existingClaim: netbox-data
# storageClass: "-" # storageClass: "-"
accessMode: ReadWriteOnce accessMode: ReadWriteOnce
size: 10Gi size: 10Gi
+1 -1
View File
@@ -1 +1 @@
REACT_APP_VERSION=1.15.4 REACT_APP_VERSION=1.17.2
+9554 -3053
View File
File diff suppressed because it is too large Load Diff
+19 -17
View File
@@ -60,25 +60,27 @@
}, },
"dependencies": { "dependencies": {
"@juggle/resize-observer": "^3.4.0", "@juggle/resize-observer": "^3.4.0",
"classnames": "^2.3.2", "classnames": "^2.5.1",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"dequal": "^2.0.3", "dequal": "^2.0.3",
"easymde": "^2.18.0", "easymde": "^2.18.0",
"history": "^5.3.0", "history": "^5.3.0",
"i18next": "^23.7.6", "i18next": "^23.11.2",
"i18next-browser-languagedetector": "^7.2.0", "i18next-browser-languagedetector": "^7.2.1",
"initials": "^3.1.2", "initials": "^3.1.2",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"linkify-react": "^4.1.3",
"linkifyjs": "^4.1.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nanoid": "^5.0.3", "nanoid": "^5.0.7",
"node-sass": "^9.0.0", "node-sass": "^9.0.0",
"photoswipe": "^5.4.2", "photoswipe": "^5.4.3",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-app-rewired": "^2.2.1", "react-app-rewired": "^2.2.1",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-datepicker": "^4.21.0", "react-datepicker": "^4.25.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-i18next": "^13.5.0", "react-i18next": "^13.5.0",
@@ -86,35 +88,35 @@
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-photoswipe-gallery": "^2.2.7", "react-photoswipe-gallery": "^2.2.7",
"react-redux": "^8.1.3", "react-redux": "^8.1.3",
"react-router-dom": "^6.19.0", "react-router-dom": "^6.22.3",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-simplemde-editor": "^5.2.0", "react-simplemde-editor": "^5.2.0",
"react-textarea-autosize": "^8.5.3", "react-textarea-autosize": "^8.5.3",
"redux": "^4.2.1", "redux": "^4.2.1",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-orm": "^0.16.2", "redux-orm": "^0.16.2",
"redux-saga": "^1.2.3", "redux-saga": "^1.3.0",
"remark-breaks": "^4.0.0", "remark-breaks": "^4.0.0",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"reselect": "^4.1.8", "reselect": "^4.1.8",
"sails.io.js": "^1.2.1", "sails.io.js": "^1.2.1",
"semantic-ui-react": "^2.1.4", "semantic-ui-react": "^2.1.5",
"socket.io-client": "^2.5.0", "socket.io-client": "^2.5.0",
"validator": "^13.11.0", "validator": "^13.11.0",
"whatwg-fetch": "^3.6.19", "whatwg-fetch": "^3.6.20",
"zxcvbn": "^4.4.2" "zxcvbn": "^4.4.2"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^6.1.4", "@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.1.0", "@testing-library/react": "^14.3.1",
"@testing-library/user-event": "^14.5.1", "@testing-library/user-event": "^14.5.2",
"babel-preset-airbnb": "^5.0.0", "babel-preset-airbnb": "^5.0.0",
"chai": "^4.3.10", "chai": "^4.4.1",
"eslint": "^8.53.0", "eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.29.0", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"react-test-renderer": "^18.2.0" "react-test-renderer": "^18.2.0"
} }
+43 -1
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, type: ActionTypes.CARD_CREATE_HANDLE,
payload: { payload: {
card, card,
cardMemberships,
cardLabels,
tasks,
attachments,
}, },
}); });
@@ -60,6 +64,34 @@ const handleCardUpdate = (card) => ({
}, },
}); });
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,
},
});
const deleteCard = (id) => ({ const deleteCard = (id) => ({
type: ActionTypes.CARD_DELETE, type: ActionTypes.CARD_DELETE,
payload: { payload: {
@@ -89,11 +121,21 @@ const handleCardDelete = (card) => ({
}, },
}); });
const filterText = (boardId, text) => ({
type: ActionTypes.TEXT_FILTER_IN_CURRENT_BOARD,
payload: {
boardId,
text,
},
});
export default { export default {
createCard, createCard,
handleCardCreate, handleCardCreate,
updateCard, updateCard,
handleCardUpdate, handleCardUpdate,
duplicateCard,
deleteCard, deleteCard,
handleCardDelete, handleCardDelete,
filterText,
}; };
+4 -2
View File
@@ -47,9 +47,11 @@ initializeCore.fetchConfig = (config) => ({
}, },
}); });
const logout = () => ({ const logout = (invalidateAccessToken) => ({
type: ActionTypes.LOGOUT, type: ActionTypes.LOGOUT,
payload: {}, payload: {
invalidateAccessToken,
},
}); });
logout.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) => ({ const deleteList = (id) => ({
type: ActionTypes.LIST_DELETE, type: ActionTypes.LIST_DELETE,
payload: { payload: {
@@ -94,6 +126,8 @@ export default {
handleListCreate, handleListCreate,
updateList, updateList,
handleListUpdate, handleListUpdate,
sortList,
handleListSort,
deleteList, deleteList,
handleListDelete, handleListDelete,
}; };
+7
View File
@@ -57,6 +57,12 @@ const updateCard = (id, data, headers) =>
item: transformCard(body.item), 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) => const deleteCard = (id, headers) =>
socket.delete(`/cards/${id}`, undefined, headers).then((body) => ({ socket.delete(`/cards/${id}`, undefined, headers).then((body) => ({
...body, ...body,
@@ -81,6 +87,7 @@ export default {
getCard, getCard,
updateCard, updateCard,
deleteCard, deleteCard,
duplicateCard,
makeHandleCardCreate, makeHandleCardCreate,
makeHandleCardUpdate, makeHandleCardUpdate,
makeHandleCardDelete, makeHandleCardDelete,
+24
View File
@@ -1,4 +1,5 @@
import socket from './socket'; import socket from './socket';
import { transformCard } from './cards';
/* Actions */ /* Actions */
@@ -7,10 +8,33 @@ const createList = (boardId, data, headers) =>
const updateList = (id, data, headers) => socket.patch(`/lists/${id}`, 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); 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 { export default {
createList, createList,
updateList, updateList,
sortList,
deleteList, deleteList,
makeHandleListSort,
}; };
@@ -13,6 +13,7 @@ const BoardActions = React.memo(
labels, labels,
filterUsers, filterUsers,
filterLabels, filterLabels,
filterText,
allUsers, allUsers,
canEdit, canEdit,
canEditMemberships, canEditMemberships,
@@ -27,6 +28,7 @@ const BoardActions = React.memo(
onLabelUpdate, onLabelUpdate,
onLabelMove, onLabelMove,
onLabelDelete, onLabelDelete,
onTextFilterUpdate,
}) => { }) => {
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
@@ -46,6 +48,7 @@ const BoardActions = React.memo(
<Filters <Filters
users={filterUsers} users={filterUsers}
labels={filterLabels} labels={filterLabels}
filterText={filterText}
allBoardMemberships={memberships} allBoardMemberships={memberships}
allLabels={labels} allLabels={labels}
canEdit={canEdit} canEdit={canEdit}
@@ -57,6 +60,7 @@ const BoardActions = React.memo(
onLabelUpdate={onLabelUpdate} onLabelUpdate={onLabelUpdate}
onLabelMove={onLabelMove} onLabelMove={onLabelMove}
onLabelDelete={onLabelDelete} onLabelDelete={onLabelDelete}
onTextFilterUpdate={onTextFilterUpdate}
/> />
</div> </div>
</div> </div>
@@ -71,6 +75,7 @@ BoardActions.propTypes = {
labels: PropTypes.array.isRequired, labels: PropTypes.array.isRequired,
filterUsers: PropTypes.array.isRequired, filterUsers: PropTypes.array.isRequired,
filterLabels: PropTypes.array.isRequired, filterLabels: PropTypes.array.isRequired,
filterText: PropTypes.string.isRequired,
allUsers: PropTypes.array.isRequired, allUsers: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */ /* eslint-enable react/forbid-prop-types */
canEdit: PropTypes.bool.isRequired, canEdit: PropTypes.bool.isRequired,
@@ -86,6 +91,7 @@ BoardActions.propTypes = {
onLabelUpdate: PropTypes.func.isRequired, onLabelUpdate: PropTypes.func.isRequired,
onLabelMove: PropTypes.func.isRequired, onLabelMove: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired, onLabelDelete: PropTypes.func.isRequired,
onTextFilterUpdate: PropTypes.func.isRequired,
}; };
export default BoardActions; export default BoardActions;
@@ -1,5 +1,7 @@
:global(#app) { :global(#app) {
.action { .action {
align-items: center;
display: flex;
flex: 0 0 auto; flex: 0 0 auto;
margin-right: 20px; margin-right: 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 PropTypes from 'prop-types';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Icon } from 'semantic-ui-react';
import { usePopup } from '../../lib/popup'; import { usePopup } from '../../lib/popup';
import { Input } from '../../lib/custom-ui';
import User from '../User'; import User from '../User';
import Label from '../Label'; import Label from '../Label';
@@ -14,6 +17,7 @@ const Filters = React.memo(
({ ({
users, users,
labels, labels,
filterText,
allBoardMemberships, allBoardMemberships,
allLabels, allLabels,
canEdit, canEdit,
@@ -25,8 +29,17 @@ const Filters = React.memo(
onLabelUpdate, onLabelUpdate,
onLabelMove, onLabelMove,
onLabelDelete, onLabelDelete,
onTextFilterUpdate,
}) => { }) => {
const [t] = useTranslation(); const [t] = useTranslation();
const [isSearchFocused, setIsSearchFocused] = useState(false);
const searchFieldRef = useRef(null);
const cancelSearch = useCallback(() => {
onTextFilterUpdate('');
searchFieldRef.current.blur();
}, [onTextFilterUpdate]);
const handleRemoveUserClick = useCallback( const handleRemoveUserClick = useCallback(
(id) => { (id) => {
@@ -42,9 +55,39 @@ const Filters = React.memo(
[onLabelRemove], [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 BoardMembershipsPopup = usePopup(BoardMembershipsStep);
const LabelsPopup = usePopup(LabelsStep); const LabelsPopup = usePopup(LabelsStep);
const isSearchActive = filterText || isSearchFocused;
return ( return (
<> <>
<span className={styles.filter}> <span className={styles.filter}>
@@ -100,6 +143,25 @@ const Filters = React.memo(
</span> </span>
))} ))}
</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 */ /* eslint-disable react/forbid-prop-types */
users: PropTypes.array.isRequired, users: PropTypes.array.isRequired,
labels: PropTypes.array.isRequired, labels: PropTypes.array.isRequired,
filterText: PropTypes.string.isRequired,
allBoardMemberships: PropTypes.array.isRequired, allBoardMemberships: PropTypes.array.isRequired,
allLabels: PropTypes.array.isRequired, allLabels: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */ /* eslint-enable react/forbid-prop-types */
@@ -121,6 +184,7 @@ Filters.propTypes = {
onLabelUpdate: PropTypes.func.isRequired, onLabelUpdate: PropTypes.func.isRequired,
onLabelMove: PropTypes.func.isRequired, onLabelMove: PropTypes.func.isRequired,
onLabelDelete: PropTypes.func.isRequired, onLabelDelete: PropTypes.func.isRequired,
onTextFilterUpdate: PropTypes.func.isRequired,
}; };
export default Filters; export default Filters;
@@ -43,4 +43,32 @@
line-height: 20px; line-height: 20px;
padding: 2px 12px; padding: 2px 12px;
} }
.search {
height: 30px;
margin: 0 12px;
transition: width 0.2s ease;
width: 280px;
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; max-height: 60vh;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
scrollbar-width: thin;
width: 100%; width: 100%;
&::-webkit-scrollbar { &::-webkit-scrollbar {
@@ -85,7 +85,6 @@
height: 56px; height: 56px;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
scrollbar-width: thin;
&:hover { &:hover {
height: 38px; height: 38px;
@@ -36,6 +36,7 @@ const ActionsStep = React.memo(
onUpdate, onUpdate,
onMove, onMove,
onTransfer, onTransfer,
onDuplicate,
onDelete, onDelete,
onUserAdd, onUserAdd,
onUserRemove, onUserRemove,
@@ -76,6 +77,11 @@ const ActionsStep = React.memo(
openStep(StepTypes.MOVE); openStep(StepTypes.MOVE);
}, [openStep]); }, [openStep]);
const handleDuplicateClick = useCallback(() => {
onDuplicate();
onClose();
}, [onDuplicate, onClose]);
const handleDeleteClick = useCallback(() => { const handleDeleteClick = useCallback(() => {
openStep(StepTypes.DELETE); openStep(StepTypes.DELETE);
}, [openStep]); }, [openStep]);
@@ -207,6 +213,11 @@ const ActionsStep = React.memo(
context: 'title', context: 'title',
})} })}
</Menu.Item> </Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleDuplicateClick}>
{t('action.duplicateCard', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}> <Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
{t('action.deleteCard', { {t('action.deleteCard', {
context: 'title', context: 'title',
@@ -232,6 +243,7 @@ ActionsStep.propTypes = {
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired, onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired, onTransfer: PropTypes.func.isRequired,
onDuplicate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
onUserAdd: PropTypes.func.isRequired, onUserAdd: PropTypes.func.isRequired,
onUserRemove: PropTypes.func.isRequired, onUserRemove: PropTypes.func.isRequired,
+3
View File
@@ -41,6 +41,7 @@ const Card = React.memo(
onUpdate, onUpdate,
onMove, onMove,
onTransfer, onTransfer,
onDuplicate,
onDelete, onDelete,
onUserAdd, onUserAdd,
onUserRemove, onUserRemove,
@@ -185,6 +186,7 @@ const Card = React.memo(
onUpdate={onUpdate} onUpdate={onUpdate}
onMove={onMove} onMove={onMove}
onTransfer={onTransfer} onTransfer={onTransfer}
onDuplicate={onDuplicate}
onDelete={onDelete} onDelete={onDelete}
onUserAdd={onUserAdd} onUserAdd={onUserAdd}
onUserRemove={onUserRemove} onUserRemove={onUserRemove}
@@ -238,6 +240,7 @@ Card.propTypes = {
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired, onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired, onTransfer: PropTypes.func.isRequired,
onDuplicate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
onUserAdd: PropTypes.func.isRequired, onUserAdd: PropTypes.func.isRequired,
onUserRemove: PropTypes.func.isRequired, onUserRemove: PropTypes.func.isRequired,
+3 -1
View File
@@ -4,6 +4,8 @@ import classNames from 'classnames';
import { Progress } from 'semantic-ui-react'; import { Progress } from 'semantic-ui-react';
import { useToggle } from '../../lib/hooks'; import { useToggle } from '../../lib/hooks';
import Linkify from '../Linkify';
import styles from './Tasks.module.scss'; import styles from './Tasks.module.scss';
const Tasks = React.memo(({ items }) => { const Tasks = React.memo(({ items }) => {
@@ -48,7 +50,7 @@ const Tasks = React.memo(({ items }) => {
key={item.id} key={item.id}
className={classNames(styles.task, item.isCompleted && styles.taskCompleted)} className={classNames(styles.task, item.isCompleted && styles.taskCompleted)}
> >
{item.name} <Linkify linkStopPropagation>{item.name}</Linkify>
</li> </li>
))} ))}
</ul> </ul>
@@ -55,8 +55,10 @@
display: block; display: block;
font-size: 12px; font-size: 12px;
line-height: 14px; line-height: 14px;
overflow: hidden;
padding-bottom: 6px; padding-bottom: 6px;
padding-left: 14px; padding-left: 14px;
text-overflow: ellipsis;
&:before { &:before {
content: "–"; content: "–";
@@ -94,7 +94,7 @@ const AttachmentAddZone = React.memo(({ children, onCreate }) => {
return ( return (
<> <>
{/* eslint-disable-next-line react/jsx-props-no-spreading */} {/* 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>} {isDragActive && <div className={styles.dropzone}>{t('common.dropFileToUpload')}</div>}
{children} {children}
{/* eslint-disable-next-line react/jsx-props-no-spreading */} {/* eslint-disable-next-line react/jsx-props-no-spreading */}
@@ -12,8 +12,4 @@
width: 100%; width: 100%;
z-index: 2001; z-index: 2001;
} }
.wrapper {
overflow: hidden;
}
} }
@@ -55,6 +55,7 @@ const CardModal = React.memo(
onUpdate, onUpdate,
onMove, onMove,
onTransfer, onTransfer,
onDuplicate,
onDelete, onDelete,
onUserAdd, onUserAdd,
onUserRemove, onUserRemove,
@@ -140,6 +141,11 @@ const CardModal = React.memo(
}); });
}, [isSubscribed, onUpdate]); }, [isSubscribed, onUpdate]);
const handleDuplicateClick = useCallback(() => {
onDuplicate();
onClose();
}, [onDuplicate, onClose]);
const handleGalleryOpen = useCallback(() => { const handleGalleryOpen = useCallback(() => {
isGalleryOpened.current = true; isGalleryOpened.current = true;
}, []); }, []);
@@ -496,6 +502,10 @@ const CardModal = React.memo(
{t('action.move')} {t('action.move')}
</Button> </Button>
</CardMovePopup> </CardMovePopup>
<Button fluid className={styles.actionButton} onClick={handleDuplicateClick}>
<Icon name="copy outline" className={styles.actionIcon} />
{t('action.duplicate')}
</Button>
<DeletePopup <DeletePopup
title="common.deleteCard" title="common.deleteCard"
content="common.areYouSureYouWantToDeleteThisCard" content="common.areYouSureYouWantToDeleteThisCard"
@@ -555,6 +565,7 @@ CardModal.propTypes = {
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired, onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired, onTransfer: PropTypes.func.isRequired,
onDuplicate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
onUserAdd: PropTypes.func.isRequired, onUserAdd: PropTypes.func.isRequired,
onUserRemove: PropTypes.func.isRequired, onUserRemove: PropTypes.func.isRequired,
@@ -8,6 +8,7 @@ import { usePopup } from '../../../lib/popup';
import NameEdit from './NameEdit'; import NameEdit from './NameEdit';
import ActionsStep from './ActionsStep'; import ActionsStep from './ActionsStep';
import Linkify from '../../Linkify';
import styles from './Item.module.scss'; import styles from './Item.module.scss';
@@ -65,7 +66,7 @@ const Item = React.memo(
onClick={handleClick} onClick={handleClick}
> >
<span className={classNames(styles.task, isCompleted && styles.taskCompleted)}> <span className={classNames(styles.task, isCompleted && styles.taskCompleted)}>
{name} <Linkify linkStopPropagation>{name}</Linkify>
</span> </span>
</span> </span>
{isPersisted && canEdit && ( {isPersisted && canEdit && (
+3 -1
View File
@@ -7,6 +7,7 @@ import { usePopup } from '../../lib/popup';
import Paths from '../../constants/Paths'; import Paths from '../../constants/Paths';
import NotificationsStep from './NotificationsStep'; import NotificationsStep from './NotificationsStep';
import User from '../User';
import UserStep from '../UserStep'; import UserStep from '../UserStep';
import styles from './Header.module.scss'; import styles from './Header.module.scss';
@@ -91,7 +92,8 @@ const Header = React.memo(
onLogout={onLogout} onLogout={onLogout}
> >
<Menu.Item className={classNames(styles.item, styles.itemHoverable)}> <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> </Menu.Item>
</UserPopup> </UserPopup>
</Menu.Menu> </Menu.Menu>
@@ -86,6 +86,10 @@
font-weight: bold; font-weight: bold;
} }
.userName {
margin-right: 10px;
}
.wrapper { .wrapper {
background: rgba(0, 0, 0, 0.24); background: rgba(0, 0, 0, 0.24);
display: flex; display: flex;
@@ -49,7 +49,6 @@
max-height: 60vh; max-height: 60vh;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
scrollbar-width: thin;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 5px; width: 5px;
@@ -25,7 +25,6 @@
max-height: 60vh; max-height: 60vh;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
scrollbar-width: thin;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 5px; 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 { Popup } from '../../lib/custom-ui';
import { useSteps } from '../../hooks'; import { useSteps } from '../../hooks';
import ListSortStep from '../ListSortStep';
import DeleteStep from '../DeleteStep'; import DeleteStep from '../DeleteStep';
import styles from './ActionsStep.module.scss'; import styles from './ActionsStep.module.scss';
const StepTypes = { const StepTypes = {
DELETE: 'DELETE', 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 [t] = useTranslation();
const [step, openStep, handleBack] = useSteps(); const [step, openStep, handleBack] = useSteps();
@@ -27,20 +29,41 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
onClose(); onClose();
}, [onCardAdd, onClose]); }, [onCardAdd, onClose]);
const handleSortClick = useCallback(() => {
openStep(StepTypes.SORT);
}, [openStep]);
const handleDeleteClick = useCallback(() => { const handleDeleteClick = useCallback(() => {
openStep(StepTypes.DELETE); openStep(StepTypes.DELETE);
}, [openStep]); }, [openStep]);
if (step && step.type === StepTypes.DELETE) { const handleSortTypeSelect = useCallback(
return ( (type) => {
<DeleteStep onSort({
title="common.deleteList" type,
content="common.areYouSureYouWantToDeleteThisList" });
buttonContent="action.deleteList"
onConfirm={onDelete} onClose();
onBack={handleBack} },
/> [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 ( return (
@@ -62,6 +85,11 @@ const ActionsStep = React.memo(({ onNameEdit, onCardAdd, onDelete, onClose }) =>
context: 'title', context: 'title',
})} })}
</Menu.Item> </Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleSortClick}>
{t('action.sortList', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}> <Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
{t('action.deleteList', { {t('action.deleteList', {
context: 'title', context: 'title',
@@ -77,6 +105,7 @@ ActionsStep.propTypes = {
onNameEdit: PropTypes.func.isRequired, onNameEdit: PropTypes.func.isRequired,
onCardAdd: PropTypes.func.isRequired, onCardAdd: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
onSort: PropTypes.func.isRequired,
onClose: 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'; import styles from './List.module.scss';
const List = React.memo( 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 [t] = useTranslation();
const [isAddCardOpened, setIsAddCardOpened] = useState(false); const [isAddCardOpened, setIsAddCardOpened] = useState(false);
@@ -114,6 +125,7 @@ const List = React.memo(
onNameEdit={handleNameEdit} onNameEdit={handleNameEdit}
onCardAdd={handleCardAdd} onCardAdd={handleCardAdd}
onDelete={onDelete} onDelete={onDelete}
onSort={onSort}
> >
<Button className={classNames(styles.headerButton, styles.target)}> <Button className={classNames(styles.headerButton, styles.target)}>
<Icon fitted name="pencil" size="small" /> <Icon fitted name="pencil" size="small" />
@@ -159,6 +171,7 @@ List.propTypes = {
cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types cardIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
canEdit: PropTypes.bool.isRequired, canEdit: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onSort: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
onCardCreate: PropTypes.func.isRequired, onCardCreate: PropTypes.func.isRequired,
}; };
@@ -43,7 +43,6 @@
max-height: calc(100vh - 268px); max-height: calc(100vh - 268px);
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
scrollbar-width: thin;
width: 290px; width: 290px;
&:hover { &:hover {
@@ -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;
+50 -40
View File
@@ -68,6 +68,7 @@ const Login = React.memo(
isSubmittingUsingOidc, isSubmittingUsingOidc,
error, error,
withOidc, withOidc,
isOidcEnforced,
onAuthenticate, onAuthenticate,
onAuthenticateUsingOidc, onAuthenticateUsingOidc,
onMessageDismiss, onMessageDismiss,
@@ -107,8 +108,10 @@ const Login = React.memo(
}, [onAuthenticate, data]); }, [onAuthenticate, data]);
useEffect(() => { useEffect(() => {
emailOrUsernameField.current.focus(); if (!isOidcEnforced) {
}, []); emailOrUsernameField.current.focus();
}
}, [isOidcEnforced]);
useEffect(() => { useEffect(() => {
if (wasSubmitting && !isSubmitting && error) { if (wasSubmitting && !isSubmitting && error) {
@@ -159,51 +162,57 @@ const Login = React.memo(
onDismiss={onMessageDismiss} onDismiss={onMessageDismiss}
/> />
)} )}
<Form size="large" onSubmit={handleSubmit}> {!isOidcEnforced && (
<div className={styles.inputWrapper}> <Form size="large" onSubmit={handleSubmit}>
<div className={styles.inputLabel}>{t('common.emailOrUsername')}</div> <div className={styles.inputWrapper}>
<Input <div className={styles.inputLabel}>{t('common.emailOrUsername')}</div>
fluid <Input
ref={emailOrUsernameField} fluid
name="emailOrUsername" ref={emailOrUsernameField}
value={data.emailOrUsername} name="emailOrUsername"
readOnly={isSubmitting} value={data.emailOrUsername}
className={styles.input} readOnly={isSubmitting}
onChange={handleFieldChange} 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> </Form>
<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>
{withOidc && ( {withOidc && (
<Button <Button
type="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} loading={isSubmittingUsingOidc}
disabled={isSubmitting || isSubmittingUsingOidc} disabled={isSubmitting || isSubmittingUsingOidc}
onClick={onAuthenticateUsingOidc} onClick={onAuthenticateUsingOidc}
> />
{t('action.logInWithSSO')}
</Button>
)} )}
</div> </div>
</div> </div>
@@ -242,6 +251,7 @@ Login.propTypes = {
isSubmittingUsingOidc: PropTypes.bool.isRequired, isSubmittingUsingOidc: PropTypes.bool.isRequired,
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
withOidc: PropTypes.bool.isRequired, withOidc: PropTypes.bool.isRequired,
isOidcEnforced: PropTypes.bool.isRequired,
onAuthenticate: PropTypes.func.isRequired, onAuthenticate: PropTypes.func.isRequired,
onAuthenticateUsingOidc: PropTypes.func.isRequired, onAuthenticateUsingOidc: PropTypes.func.isRequired,
onMessageDismiss: PropTypes.func.isRequired, onMessageDismiss: PropTypes.func.isRequired,
@@ -3,6 +3,7 @@ import React, { useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button } from 'semantic-ui-react'; import { Button } from 'semantic-ui-react';
import { Popup } from '../../lib/custom-ui';
import { useSteps } from '../../hooks'; import { useSteps } from '../../hooks';
import User from '../User'; import User from '../User';
@@ -19,6 +20,7 @@ const ActionsStep = React.memo(
({ ({
membership, membership,
permissionsSelectStep, permissionsSelectStep,
title,
leaveButtonContent, leaveButtonContent,
leaveConfirmationTitle, leaveConfirmationTitle,
leaveConfirmationContent, leaveConfirmationContent,
@@ -31,6 +33,7 @@ const ActionsStep = React.memo(
canLeave, canLeave,
onUpdate, onUpdate,
onDelete, onDelete,
onBack,
onClose, onClose,
}) => { }) => {
const [t] = useTranslation(); const [t] = useTranslation();
@@ -53,6 +56,11 @@ const ActionsStep = React.memo(
[onUpdate], [onUpdate],
); );
const handleDeleteConfirm = useCallback(() => {
onDelete();
onClose();
}, [onDelete, onClose]);
if (step) { if (step) {
switch (step.type) { switch (step.type) {
case StepTypes.EDIT_PERMISSIONS: { case StepTypes.EDIT_PERMISSIONS: {
@@ -81,7 +89,7 @@ const ActionsStep = React.memo(
? leaveConfirmationButtonContent ? leaveConfirmationButtonContent
: deleteConfirmationButtonContent : deleteConfirmationButtonContent
} }
onConfirm={onDelete} onConfirm={handleDeleteConfirm}
onBack={handleBack} onBack={handleBack}
/> />
); );
@@ -89,7 +97,7 @@ const ActionsStep = React.memo(
} }
} }
return ( const contentNode = (
<> <>
<span className={styles.user}> <span className={styles.user}>
<User name={membership.user.name} avatarUrl={membership.user.avatarUrl} size="large" /> <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 = { ActionsStep.propTypes = {
membership: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types membership: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
permissionsSelectStep: PropTypes.elementType, permissionsSelectStep: PropTypes.elementType,
title: PropTypes.string,
leaveButtonContent: PropTypes.string, leaveButtonContent: PropTypes.string,
leaveConfirmationTitle: PropTypes.string, leaveConfirmationTitle: PropTypes.string,
leaveConfirmationContent: PropTypes.string, leaveConfirmationContent: PropTypes.string,
@@ -143,11 +165,13 @@ ActionsStep.propTypes = {
canLeave: PropTypes.bool.isRequired, canLeave: PropTypes.bool.isRequired,
onUpdate: PropTypes.func, onUpdate: PropTypes.func,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
onBack: PropTypes.func,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
}; };
ActionsStep.defaultProps = { ActionsStep.defaultProps = {
permissionsSelectStep: undefined, permissionsSelectStep: undefined,
title: 'common.memberActions',
leaveButtonContent: 'action.leaveBoard', leaveButtonContent: 'action.leaveBoard',
leaveConfirmationTitle: 'common.leaveBoard', leaveConfirmationTitle: 'common.leaveBoard',
leaveConfirmationContent: 'common.areYouSureYouWantToLeaveBoard', leaveConfirmationContent: 'common.areYouSureYouWantToLeaveBoard',
@@ -157,6 +181,7 @@ ActionsStep.defaultProps = {
deleteConfirmationContent: 'common.areYouSureYouWantToRemoveThisMemberFromBoard', deleteConfirmationContent: 'common.areYouSureYouWantToRemoveThisMemberFromBoard',
deleteConfirmationButtonContent: 'action.removeMember', deleteConfirmationButtonContent: 'action.removeMember',
onUpdate: undefined, onUpdate: undefined,
onBack: undefined,
}; };
export default ActionsStep; export default ActionsStep;
@@ -4,7 +4,6 @@
max-height: 60vh; max-height: 60vh;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
scrollbar-width: thin;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 5px; width: 5px;
@@ -5,16 +5,21 @@ import { usePopup } from '../../lib/popup';
import AddStep from './AddStep'; import AddStep from './AddStep';
import ActionsStep from './ActionsStep'; import ActionsStep from './ActionsStep';
import MembershipsStep from './MembershipsStep';
import User from '../User'; import User from '../User';
import styles from './Memberships.module.scss'; import styles from './Memberships.module.scss';
const MAX_MEMBERS = 6;
const Memberships = React.memo( const Memberships = React.memo(
({ ({
items, items,
allUsers, allUsers,
permissionsSelectStep, permissionsSelectStep,
title,
addTitle, addTitle,
actionsTitle,
leaveButtonContent, leaveButtonContent,
leaveConfirmationTitle, leaveConfirmationTitle,
leaveConfirmationContent, leaveConfirmationContent,
@@ -31,11 +36,14 @@ const Memberships = React.memo(
}) => { }) => {
const AddPopup = usePopup(AddStep); const AddPopup = usePopup(AddStep);
const ActionsPopup = usePopup(ActionsStep); const ActionsPopup = usePopup(ActionsStep);
const MembershipsPopup = usePopup(MembershipsStep);
const remainMembersCount = items.length - MAX_MEMBERS;
return ( return (
<> <>
<span className={styles.users}> <span className={styles.users}>
{items.map((item) => ( {items.slice(0, MAX_MEMBERS).map((item) => (
<span key={item.id} className={styles.user}> <span key={item.id} className={styles.user}>
<ActionsPopup <ActionsPopup
membership={item} membership={item}
@@ -63,6 +71,30 @@ const Memberships = React.memo(
</span> </span>
))} ))}
</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 && ( {canEdit && (
<AddPopup <AddPopup
users={allUsers} users={allUsers}
@@ -85,7 +117,9 @@ Memberships.propTypes = {
allUsers: PropTypes.array.isRequired, allUsers: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */ /* eslint-enable react/forbid-prop-types */
permissionsSelectStep: PropTypes.elementType, permissionsSelectStep: PropTypes.elementType,
title: PropTypes.string,
addTitle: PropTypes.string, addTitle: PropTypes.string,
actionsTitle: PropTypes.string,
leaveButtonContent: PropTypes.string, leaveButtonContent: PropTypes.string,
leaveConfirmationTitle: PropTypes.string, leaveConfirmationTitle: PropTypes.string,
leaveConfirmationContent: PropTypes.string, leaveConfirmationContent: PropTypes.string,
@@ -103,7 +137,9 @@ Memberships.propTypes = {
Memberships.defaultProps = { Memberships.defaultProps = {
permissionsSelectStep: undefined, permissionsSelectStep: undefined,
title: undefined,
addTitle: undefined, addTitle: undefined,
actionsTitle: undefined,
leaveButtonContent: undefined, leaveButtonContent: undefined,
leaveConfirmationTitle: undefined, leaveConfirmationTitle: undefined,
leaveConfirmationContent: undefined, leaveConfirmationContent: undefined,
@@ -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 <Memberships
items={items} items={items}
allUsers={allUsers} allUsers={allUsers}
title="common.managers"
addTitle="common.addManager" addTitle="common.addManager"
actionsTitle="common.managerActions"
leaveButtonContent="action.leaveProject" leaveButtonContent="action.leaveProject"
leaveConfirmationTitle="common.leaveProject" leaveConfirmationTitle="common.leaveProject"
leaveConfirmationContent="common.areYouSureYouWantToLeaveProject" leaveConfirmationContent="common.areYouSureYouWantToLeaveProject"
@@ -24,6 +24,7 @@ const AccountPane = React.memo(
organization, organization,
language, language,
isLocked, isLocked,
isUsernameLocked,
isAvatarUpdating, isAvatarUpdating,
usernameUpdateForm, usernameUpdateForm,
emailUpdateForm, emailUpdateForm,
@@ -104,7 +105,7 @@ const AccountPane = React.memo(
value={language || 'auto'} value={language || 'auto'}
onChange={handleLanguageChange} onChange={handleLanguageChange}
/> />
{!isLocked && ( {(!isLocked || !isUsernameLocked) && (
<> <>
<Divider horizontal section> <Divider horizontal section>
<Header as="h4"> <Header as="h4">
@@ -113,56 +114,62 @@ const AccountPane = React.memo(
})} })}
</Header> </Header>
</Divider> </Divider>
<div className={styles.action}> {!isUsernameLocked && (
<UserUsernameEditPopup <div className={styles.action}>
usePasswordConfirmation <UserUsernameEditPopup
defaultData={usernameUpdateForm.data} defaultData={usernameUpdateForm.data}
username={username} username={username}
isSubmitting={usernameUpdateForm.isSubmitting} isSubmitting={usernameUpdateForm.isSubmitting}
error={usernameUpdateForm.error} error={usernameUpdateForm.error}
onUpdate={onUsernameUpdate} usePasswordConfirmation={!isLocked} // FIXME: hack
onMessageDismiss={onUsernameUpdateMessageDismiss} onUpdate={onUsernameUpdate}
> onMessageDismiss={onUsernameUpdateMessageDismiss}
<Button className={styles.actionButton}> >
{t('action.editUsername', { <Button className={styles.actionButton}>
context: 'title', {t('action.editUsername', {
})} context: 'title',
</Button> })}
</UserUsernameEditPopup> </Button>
</div> </UserUsernameEditPopup>
<div className={styles.action}> </div>
<UserEmailEditPopup )}
usePasswordConfirmation {!isLocked && (
defaultData={emailUpdateForm.data} <>
email={email} <div className={styles.action}>
isSubmitting={emailUpdateForm.isSubmitting} <UserEmailEditPopup
error={emailUpdateForm.error} usePasswordConfirmation
onUpdate={onEmailUpdate} defaultData={emailUpdateForm.data}
onMessageDismiss={onEmailUpdateMessageDismiss} email={email}
> isSubmitting={emailUpdateForm.isSubmitting}
<Button className={styles.actionButton}> error={emailUpdateForm.error}
{t('action.editEmail', { onUpdate={onEmailUpdate}
context: 'title', onMessageDismiss={onEmailUpdateMessageDismiss}
})} >
</Button> <Button className={styles.actionButton}>
</UserEmailEditPopup> {t('action.editEmail', {
</div> context: 'title',
<div className={styles.action}> })}
<UserPasswordEditPopup </Button>
usePasswordConfirmation </UserEmailEditPopup>
defaultData={passwordUpdateForm.data} </div>
isSubmitting={passwordUpdateForm.isSubmitting} <div className={styles.action}>
error={passwordUpdateForm.error} <UserPasswordEditPopup
onUpdate={onPasswordUpdate} usePasswordConfirmation
onMessageDismiss={onPasswordUpdateMessageDismiss} defaultData={passwordUpdateForm.data}
> isSubmitting={passwordUpdateForm.isSubmitting}
<Button className={styles.actionButton}> error={passwordUpdateForm.error}
{t('action.editPassword', { onUpdate={onPasswordUpdate}
context: 'title', onMessageDismiss={onPasswordUpdateMessageDismiss}
})} >
</Button> <Button className={styles.actionButton}>
</UserPasswordEditPopup> {t('action.editPassword', {
</div> context: 'title',
})}
</Button>
</UserPasswordEditPopup>
</div>
</>
)}
</> </>
)} )}
</Tab.Pane> </Tab.Pane>
@@ -179,6 +186,7 @@ AccountPane.propTypes = {
organization: PropTypes.string, organization: PropTypes.string,
language: PropTypes.string, language: PropTypes.string,
isLocked: PropTypes.bool.isRequired, isLocked: PropTypes.bool.isRequired,
isUsernameLocked: PropTypes.bool.isRequired,
isAvatarUpdating: PropTypes.bool.isRequired, isAvatarUpdating: PropTypes.bool.isRequired,
/* eslint-disable react/forbid-prop-types */ /* eslint-disable react/forbid-prop-types */
usernameUpdateForm: PropTypes.object.isRequired, usernameUpdateForm: PropTypes.object.isRequired,
@@ -18,6 +18,7 @@ const UserSettingsModal = React.memo(
language, language,
subscribeToOwnCards, subscribeToOwnCards,
isLocked, isLocked,
isUsernameLocked,
isAvatarUpdating, isAvatarUpdating,
usernameUpdateForm, usernameUpdateForm,
emailUpdateForm, emailUpdateForm,
@@ -50,6 +51,7 @@ const UserSettingsModal = React.memo(
organization={organization} organization={organization}
language={language} language={language}
isLocked={isLocked} isLocked={isLocked}
isUsernameLocked={isUsernameLocked}
isAvatarUpdating={isAvatarUpdating} isAvatarUpdating={isAvatarUpdating}
usernameUpdateForm={usernameUpdateForm} usernameUpdateForm={usernameUpdateForm}
emailUpdateForm={emailUpdateForm} emailUpdateForm={emailUpdateForm}
@@ -108,6 +110,7 @@ UserSettingsModal.propTypes = {
language: PropTypes.string, language: PropTypes.string,
subscribeToOwnCards: PropTypes.bool.isRequired, subscribeToOwnCards: PropTypes.bool.isRequired,
isLocked: PropTypes.bool.isRequired, isLocked: PropTypes.bool.isRequired,
isUsernameLocked: PropTypes.bool.isRequired,
isAvatarUpdating: PropTypes.bool.isRequired, isAvatarUpdating: PropTypes.bool.isRequired,
/* eslint-disable react/forbid-prop-types */ /* eslint-disable react/forbid-prop-types */
usernameUpdateForm: PropTypes.object.isRequired, usernameUpdateForm: PropTypes.object.isRequired,
@@ -136,13 +136,15 @@ const ActionsStep = React.memo(
context: 'title', context: 'title',
})} })}
</Menu.Item> </Menu.Item>
{!user.isUsernameLocked && (
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
{t('action.editUsername', {
context: 'title',
})}
</Menu.Item>
)}
{!user.isLocked && ( {!user.isLocked && (
<> <>
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
{t('action.editUsername', {
context: 'title',
})}
</Menu.Item>
<Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}> <Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}>
{t('action.editEmail', { {t('action.editEmail', {
context: 'title', context: 'title',
@@ -19,6 +19,7 @@ const Item = React.memo(
isAdmin, isAdmin,
isLocked, isLocked,
isRoleLocked, isRoleLocked,
isUsernameLocked,
isDeletionLocked, isDeletionLocked,
emailUpdateForm, emailUpdateForm,
passwordUpdateForm, passwordUpdateForm,
@@ -61,6 +62,7 @@ const Item = React.memo(
phone, phone,
isAdmin, isAdmin,
isLocked, isLocked,
isUsernameLocked,
isDeletionLocked, isDeletionLocked,
emailUpdateForm, emailUpdateForm,
passwordUpdateForm, passwordUpdateForm,
@@ -95,6 +97,7 @@ Item.propTypes = {
isAdmin: PropTypes.bool.isRequired, isAdmin: PropTypes.bool.isRequired,
isLocked: PropTypes.bool.isRequired, isLocked: PropTypes.bool.isRequired,
isRoleLocked: PropTypes.bool.isRequired, isRoleLocked: PropTypes.bool.isRequired,
isUsernameLocked: PropTypes.bool.isRequired,
isDeletionLocked: PropTypes.bool.isRequired, isDeletionLocked: PropTypes.bool.isRequired,
/* eslint-disable react/forbid-prop-types */ /* eslint-disable react/forbid-prop-types */
emailUpdateForm: PropTypes.object.isRequired, emailUpdateForm: PropTypes.object.isRequired,
@@ -10,6 +10,7 @@ import Item from './Item';
const UsersModal = React.memo( const UsersModal = React.memo(
({ ({
items, items,
canAdd,
onUpdate, onUpdate,
onUsernameUpdate, onUsernameUpdate,
onUsernameUpdateMessageDismiss, onUsernameUpdateMessageDismiss,
@@ -112,6 +113,7 @@ const UsersModal = React.memo(
isAdmin={item.isAdmin} isAdmin={item.isAdmin}
isLocked={item.isLocked} isLocked={item.isLocked}
isRoleLocked={item.isRoleLocked} isRoleLocked={item.isRoleLocked}
isUsernameLocked={item.isUsernameLocked}
isDeletionLocked={item.isDeletionLocked} isDeletionLocked={item.isDeletionLocked}
emailUpdateForm={item.emailUpdateForm} emailUpdateForm={item.emailUpdateForm}
passwordUpdateForm={item.passwordUpdateForm} passwordUpdateForm={item.passwordUpdateForm}
@@ -129,11 +131,13 @@ const UsersModal = React.memo(
</Table.Body> </Table.Body>
</Table> </Table>
</Modal.Content> </Modal.Content>
<Modal.Actions> {canAdd && (
<UserAddPopupContainer> <Modal.Actions>
<Button positive content={t('action.addUser')} /> <UserAddPopupContainer>
</UserAddPopupContainer> <Button positive content={t('action.addUser')} />
</Modal.Actions> </UserAddPopupContainer>
</Modal.Actions>
)}
</Modal> </Modal>
); );
}, },
@@ -141,6 +145,7 @@ const UsersModal = React.memo(
UsersModal.propTypes = { UsersModal.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
canAdd: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onUsernameUpdate: PropTypes.func.isRequired, onUsernameUpdate: PropTypes.func.isRequired,
onUsernameUpdateMessageDismiss: PropTypes.func.isRequired, onUsernameUpdateMessageDismiss: PropTypes.func.isRequired,
+8
View File
@@ -173,6 +173,10 @@ export default {
LIST_UPDATE__SUCCESS: 'LIST_UPDATE__SUCCESS', LIST_UPDATE__SUCCESS: 'LIST_UPDATE__SUCCESS',
LIST_UPDATE__FAILURE: 'LIST_UPDATE__FAILURE', LIST_UPDATE__FAILURE: 'LIST_UPDATE__FAILURE',
LIST_UPDATE_HANDLE: 'LIST_UPDATE_HANDLE', 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: 'LIST_DELETE',
LIST_DELETE__SUCCESS: 'LIST_DELETE__SUCCESS', LIST_DELETE__SUCCESS: 'LIST_DELETE__SUCCESS',
LIST_DELETE__FAILURE: 'LIST_DELETE__FAILURE', LIST_DELETE__FAILURE: 'LIST_DELETE__FAILURE',
@@ -194,10 +198,14 @@ export default {
CARD_TRANSFER: 'CARD_TRANSFER', CARD_TRANSFER: 'CARD_TRANSFER',
CARD_TRANSFER__SUCCESS: 'CARD_TRANSFER__SUCCESS', CARD_TRANSFER__SUCCESS: 'CARD_TRANSFER__SUCCESS',
CARD_TRANSFER__FAILURE: 'CARD_TRANSFER__FAILURE', 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: 'CARD_DELETE',
CARD_DELETE__SUCCESS: 'CARD_DELETE__SUCCESS', CARD_DELETE__SUCCESS: 'CARD_DELETE__SUCCESS',
CARD_DELETE__FAILURE: 'CARD_DELETE__FAILURE', CARD_DELETE__FAILURE: 'CARD_DELETE__FAILURE',
CARD_DELETE_HANDLE: 'CARD_DELETE_HANDLE', CARD_DELETE_HANDLE: 'CARD_DELETE_HANDLE',
TEXT_FILTER_IN_CURRENT_BOARD: 'TEXT_FILTER_IN_CURRENT_BOARD',
/* Tasks */ /* Tasks */
+5
View File
@@ -120,6 +120,8 @@ export default {
LIST_MOVE: `${PREFIX}/LIST_MOVE`, LIST_MOVE: `${PREFIX}/LIST_MOVE`,
LIST_DELETE: `${PREFIX}/LIST_DELETE`, LIST_DELETE: `${PREFIX}/LIST_DELETE`,
LIST_DELETE_HANDLE: `${PREFIX}/LIST_DELETE_HANDLE`, LIST_DELETE_HANDLE: `${PREFIX}/LIST_DELETE_HANDLE`,
LIST_SORT: `${PREFIX}/LIST_SORT`,
LIST_SORT_HANDLE: `${PREFIX}/LIST_SORT_HANDLE`,
/* Cards */ /* Cards */
@@ -132,9 +134,12 @@ export default {
CURRENT_CARD_MOVE: `${PREFIX}/CURRENT_CARD_MOVE`, CURRENT_CARD_MOVE: `${PREFIX}/CURRENT_CARD_MOVE`,
CARD_TRANSFER: `${PREFIX}/CARD_TRANSFER`, CARD_TRANSFER: `${PREFIX}/CARD_TRANSFER`,
CURRENT_CARD_TRANSFER: `${PREFIX}/CURRENT_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`, CARD_DELETE: `${PREFIX}/CARD_DELETE`,
CURRENT_CARD_DELETE: `${PREFIX}/CURRENT_CARD_DELETE`, CURRENT_CARD_DELETE: `${PREFIX}/CURRENT_CARD_DELETE`,
CARD_DELETE_HANDLE: `${PREFIX}/CARD_DELETE_HANDLE`, CARD_DELETE_HANDLE: `${PREFIX}/CARD_DELETE_HANDLE`,
TEXT_FILTER_IN_CURRENT_BOARD: `${PREFIX}/FILTER_TEXT_HANDLE`,
/* Tasks */ /* Tasks */
+7
View File
@@ -8,6 +8,13 @@ export const BoardMembershipRoles = {
VIEWER: 'viewer', 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 = { export const ActivityTypes = {
CREATE_CARD: 'createCard', CREATE_CARD: 'createCard',
MOVE_CARD: 'moveCard', MOVE_CARD: 'moveCard',
@@ -13,6 +13,7 @@ const mapStateToProps = (state) => {
const labels = selectors.selectLabelsForCurrentBoard(state); const labels = selectors.selectLabelsForCurrentBoard(state);
const filterUsers = selectors.selectFilterUsersForCurrentBoard(state); const filterUsers = selectors.selectFilterUsersForCurrentBoard(state);
const filterLabels = selectors.selectFilterLabelsForCurrentBoard(state); const filterLabels = selectors.selectFilterLabelsForCurrentBoard(state);
const filterText = selectors.selectFilterTextForCurrentBoard(state);
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state); const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
const isCurrentUserEditor = const isCurrentUserEditor =
@@ -23,6 +24,7 @@ const mapStateToProps = (state) => {
labels, labels,
filterUsers, filterUsers,
filterLabels, filterLabels,
filterText,
allUsers, allUsers,
canEdit: isCurrentUserEditor, canEdit: isCurrentUserEditor,
canEditMemberships: isCurrentUserManager, canEditMemberships: isCurrentUserManager,
@@ -43,6 +45,7 @@ const mapDispatchToProps = (dispatch) =>
onLabelUpdate: entryActions.updateLabel, onLabelUpdate: entryActions.updateLabel,
onLabelMove: entryActions.moveLabel, onLabelMove: entryActions.moveLabel,
onLabelDelete: entryActions.deleteLabel, onLabelDelete: entryActions.deleteLabel,
onTextFilterUpdate: entryActions.filterText,
}, },
dispatch, dispatch,
); );
+1
View File
@@ -62,6 +62,7 @@ const mapDispatchToProps = (dispatch, { id }) =>
onUpdate: (data) => entryActions.updateCard(id, data), onUpdate: (data) => entryActions.updateCard(id, data),
onMove: (listId, index) => entryActions.moveCard(id, listId, index), onMove: (listId, index) => entryActions.moveCard(id, listId, index),
onTransfer: (boardId, listId) => entryActions.transferCard(id, boardId, listId), onTransfer: (boardId, listId) => entryActions.transferCard(id, boardId, listId),
onDuplicate: () => entryActions.duplicateCard(id),
onDelete: () => entryActions.deleteCard(id), onDelete: () => entryActions.deleteCard(id),
onUserAdd: (userId) => entryActions.addUserToCard(userId, id), onUserAdd: (userId) => entryActions.addUserToCard(userId, id),
onUserRemove: (userId) => entryActions.removeUserFromCard(userId, id), onUserRemove: (userId) => entryActions.removeUserFromCard(userId, id),
@@ -78,6 +78,7 @@ const mapDispatchToProps = (dispatch) =>
onUpdate: entryActions.updateCurrentCard, onUpdate: entryActions.updateCurrentCard,
onMove: entryActions.moveCurrentCard, onMove: entryActions.moveCurrentCard,
onTransfer: entryActions.transferCurrentCard, onTransfer: entryActions.transferCurrentCard,
onDuplicate: entryActions.duplicateCurrentCard,
onDelete: entryActions.deleteCurrentCard, onDelete: entryActions.deleteCurrentCard,
onUserAdd: entryActions.addUserToCurrentCard, onUserAdd: entryActions.addUserToCurrentCard,
onUserRemove: entryActions.removeUserFromCurrentCard, onUserRemove: entryActions.removeUserFromCurrentCard,
+1
View File
@@ -33,6 +33,7 @@ const mapDispatchToProps = (dispatch, { id }) =>
bindActionCreators( bindActionCreators(
{ {
onUpdate: (data) => entryActions.updateList(id, data), onUpdate: (data) => entryActions.updateList(id, data),
onSort: (data) => entryActions.sortList(id, data),
onDelete: () => entryActions.deleteList(id), onDelete: () => entryActions.deleteList(id),
onCardCreate: (data, autoOpen) => entryActions.createCard(id, data, autoOpen), onCardCreate: (data, autoOpen) => entryActions.createCard(id, data, autoOpen),
}, },
+1
View File
@@ -20,6 +20,7 @@ const mapStateToProps = (state) => {
isSubmittingUsingOidc, isSubmittingUsingOidc,
error, error,
withOidc: !!oidcConfig, withOidc: !!oidcConfig,
isOidcEnforced: !!oidcConfig && oidcConfig.isEnforced,
}; };
}; };
@@ -16,6 +16,7 @@ const mapStateToProps = (state) => {
language, language,
subscribeToOwnCards, subscribeToOwnCards,
isLocked, isLocked,
isUsernameLocked,
isAvatarUpdating, isAvatarUpdating,
emailUpdateForm, emailUpdateForm,
passwordUpdateForm, passwordUpdateForm,
@@ -32,6 +33,7 @@ const mapStateToProps = (state) => {
language, language,
subscribeToOwnCards, subscribeToOwnCards,
isLocked, isLocked,
isUsernameLocked,
isAvatarUpdating, isAvatarUpdating,
emailUpdateForm, emailUpdateForm,
passwordUpdateForm, passwordUpdateForm,
@@ -6,10 +6,12 @@ import entryActions from '../entry-actions';
import UsersModal from '../components/UsersModal'; import UsersModal from '../components/UsersModal';
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const oidcConfig = selectors.selectOidcConfig(state);
const users = selectors.selectUsersExceptCurrent(state); const users = selectors.selectUsersExceptCurrent(state);
return { return {
items: users, items: users,
canAdd: !oidcConfig || !oidcConfig.isEnforced,
}; };
}; };
+26 -4
View File
@@ -38,7 +38,7 @@ const handleCardUpdate = (card) => ({
}, },
}); });
const moveCard = (id, listId, index = 0) => ({ const moveCard = (id, listId, index) => ({
type: EntryActionTypes.CARD_MOVE, type: EntryActionTypes.CARD_MOVE,
payload: { payload: {
id, id,
@@ -47,7 +47,7 @@ const moveCard = (id, listId, index = 0) => ({
}, },
}); });
const moveCurrentCard = (listId, index = 0) => ({ const moveCurrentCard = (listId, index) => ({
type: EntryActionTypes.CURRENT_CARD_MOVE, type: EntryActionTypes.CURRENT_CARD_MOVE,
payload: { payload: {
listId, listId,
@@ -55,7 +55,7 @@ const moveCurrentCard = (listId, index = 0) => ({
}, },
}); });
const transferCard = (id, boardId, listId, index = 0) => ({ const transferCard = (id, boardId, listId, index) => ({
type: EntryActionTypes.CARD_TRANSFER, type: EntryActionTypes.CARD_TRANSFER,
payload: { payload: {
id, id,
@@ -65,7 +65,7 @@ const transferCard = (id, boardId, listId, index = 0) => ({
}, },
}); });
const transferCurrentCard = (boardId, listId, index = 0) => ({ const transferCurrentCard = (boardId, listId, index) => ({
type: EntryActionTypes.CURRENT_CARD_TRANSFER, type: EntryActionTypes.CURRENT_CARD_TRANSFER,
payload: { payload: {
boardId, boardId,
@@ -74,6 +74,18 @@ const transferCurrentCard = (boardId, listId, index = 0) => ({
}, },
}); });
const duplicateCard = (id) => ({
type: EntryActionTypes.CARD_DUPLICATE,
payload: {
id,
},
});
const duplicateCurrentCard = () => ({
type: EntryActionTypes.CURRENT_CARD_DUPLICATE,
payload: {},
});
const deleteCard = (id) => ({ const deleteCard = (id) => ({
type: EntryActionTypes.CARD_DELETE, type: EntryActionTypes.CARD_DELETE,
payload: { payload: {
@@ -93,6 +105,13 @@ const handleCardDelete = (card) => ({
}, },
}); });
const filterText = (text) => ({
type: EntryActionTypes.TEXT_FILTER_IN_CURRENT_BOARD,
payload: {
text,
},
});
export default { export default {
createCard, createCard,
handleCardCreate, handleCardCreate,
@@ -103,7 +122,10 @@ export default {
moveCurrentCard, moveCurrentCard,
transferCard, transferCard,
transferCurrentCard, transferCurrentCard,
duplicateCard,
duplicateCurrentCard,
deleteCard, deleteCard,
deleteCurrentCard, deleteCurrentCard,
handleCardDelete, handleCardDelete,
filterText,
}; };
+4 -2
View File
@@ -1,8 +1,10 @@
import EntryActionTypes from '../constants/EntryActionTypes'; import EntryActionTypes from '../constants/EntryActionTypes';
const logout = () => ({ const logout = (invalidateAccessToken) => ({
type: EntryActionTypes.LOGOUT, type: EntryActionTypes.LOGOUT,
payload: {}, payload: {
invalidateAccessToken,
},
}); });
export default { export default {
+20
View File
@@ -37,6 +37,24 @@ const moveList = (id, index) => ({
}, },
}); });
const sortList = (id, data) => {
return {
type: EntryActionTypes.LIST_SORT,
payload: {
id,
data,
},
};
};
const handleListSort = (list, cards) => ({
type: EntryActionTypes.LIST_SORT_HANDLE,
payload: {
list,
cards,
},
});
const deleteList = (id) => ({ const deleteList = (id) => ({
type: EntryActionTypes.LIST_DELETE, type: EntryActionTypes.LIST_DELETE,
payload: { payload: {
@@ -57,6 +75,8 @@ export default {
updateList, updateList,
handleListUpdate, handleListUpdate,
moveList, moveList,
sortList,
handleListSort,
deleteList, deleteList,
handleListDelete, handleListDelete,
}; };
@@ -9,4 +9,6 @@ export default class Input extends SemanticUIInput {
static Mask = InputMask; static Mask = InputMask;
focus = (options) => this.inputRef.current.focus(options); focus = (options) => this.inputRef.current.focus(options);
blur = () => this.inputRef.current.blur();
} }
+17 -8
View File
@@ -39,9 +39,9 @@ export default {
areYouSureYouWantToLeaveBoard: 'Opravdu chcete opustit tuto tabuli?', areYouSureYouWantToLeaveBoard: 'Opravdu chcete opustit tuto tabuli?',
areYouSureYouWantToLeaveProject: 'Opravdu chcete opustit projekt?', areYouSureYouWantToLeaveProject: 'Opravdu chcete opustit projekt?',
areYouSureYouWantToRemoveThisManagerFromProject: areYouSureYouWantToRemoveThisManagerFromProject:
'Are you sure you want to remove this manager from the project?', 'Jste si jisti, že chcete tohoto správce z projektu odebrat?',
areYouSureYouWantToRemoveThisMemberFromBoard: areYouSureYouWantToRemoveThisMemberFromBoard:
'Opravdu chcete odstranit tohoto člena z tabule?', 'Jste si jisti, že chcete tohoto člena odebrat z tabule?',
attachment: 'Příloha', attachment: 'Příloha',
attachments: 'Přílohy', attachments: 'Přílohy',
authentication: 'Ověření', authentication: 'Ověření',
@@ -55,6 +55,7 @@ export default {
cardNotFound_title: 'Karta nenalezena', cardNotFound_title: 'Karta nenalezena',
cardOrActionAreDeleted: 'Karta nebo akce je smazána.', cardOrActionAreDeleted: 'Karta nebo akce je smazána.',
color: 'Barva', color: 'Barva',
copy_inline: 'copy',
createBoard_title: 'Vytvořit tabuli', createBoard_title: 'Vytvořit tabuli',
createLabel_title: 'Vytvořit štítek', createLabel_title: 'Vytvořit štítek',
createNewOneOrSelectExistingOne: 'Vytvořit nový nebo vyberte<br />již existující.', createNewOneOrSelectExistingOne: 'Vytvořit nový nebo vyberte<br />již existující.',
@@ -63,7 +64,8 @@ export default {
currentPassword: 'Aktuální heslo', currentPassword: 'Aktuální heslo',
dangerZone_title: 'Nebezpečná zóna', dangerZone_title: 'Nebezpečná zóna',
date: 'Datum', date: 'Datum',
dueDate_title: 'Termín do', dueDate: 'Termín',
dueDate_title: 'Termín',
deleteAttachment_title: 'Smazat přílohu', deleteAttachment_title: 'Smazat přílohu',
deleteBoard_title: 'Smazat tabuli', deleteBoard_title: 'Smazat tabuli',
deleteCard_title: 'Smazat kartu', deleteCard_title: 'Smazat kartu',
@@ -80,7 +82,7 @@ export default {
editAttachment_title: 'Upravit přílohu', editAttachment_title: 'Upravit přílohu',
editAvatar_title: 'Upravit avatar', editAvatar_title: 'Upravit avatar',
editBoard_title: 'Upravit tabuli', editBoard_title: 'Upravit tabuli',
editDueDate_title: 'Upravit Termín do', editDueDate_title: 'Upravit termín',
editEmail_title: 'Upravit e-mail', editEmail_title: 'Upravit e-mail',
editInformation_title: 'Upravit informace', editInformation_title: 'Upravit informace',
editLabel_title: 'Upravit štítek', editLabel_title: 'Upravit štítek',
@@ -115,6 +117,7 @@ export default {
minutes: 'Minuty', minutes: 'Minuty',
moveCard_title: 'Přesunout kartu', moveCard_title: 'Přesunout kartu',
name: 'Jméno', name: 'Jméno',
newestFirst: 'Nejnovější',
newEmail: 'Nový e-mail', newEmail: 'Nový e-mail',
newPassword: 'Nové heslo', newPassword: 'Nové heslo',
newUsername: 'Nové uživatelské jméno', newUsername: 'Nové uživatelské jméno',
@@ -124,13 +127,14 @@ export default {
noProjects: 'Žádné projekty', noProjects: 'Žádné projekty',
notifications: 'Oznámení', notifications: 'Oznámení',
noUnreadNotifications: 'Žádné nepřečtené oznámení', noUnreadNotifications: 'Žádné nepřečtené oznámení',
oldestFirst: 'Nejstarší',
openBoard_title: 'Otevřít tabuli', openBoard_title: 'Otevřít tabuli',
optional_inline: 'volitelné', optional_inline: 'volitelné',
organization: 'Společnost', organization: 'Společnost',
phone: 'Telefon', phone: 'Telefon',
preferences: 'Volby', preferences: 'Volby',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: dejte Ctrl-V (Cmd-V na Mac) pro vložení přílohy ze schránky.', 'Tip: stisknutím Ctrl-V (Cmd-V na Mac) přidáte přílohu ze schránky.',
project: 'Projekt', project: 'Projekt',
projectNotFound_title: 'Projekt nenalezen', projectNotFound_title: 'Projekt nenalezen',
removeManager_title: 'Odstranit vedoucího', removeManager_title: 'Odstranit vedoucího',
@@ -138,12 +142,14 @@ export default {
searchLabels: 'Hledat štítky...', searchLabels: 'Hledat štítky...',
searchMembers: 'Hledat členy...', searchMembers: 'Hledat členy...',
searchUsers: 'Hledat uživatele...', searchUsers: 'Hledat uživatele...',
searchCards: 'Hledat karty...',
seconds: 'Vteřin', seconds: 'Vteřin',
selectBoard: 'Vybrat tabuli', selectBoard: 'Vybrat tabuli',
selectList: 'Vybrat seznam', selectList: 'Vybrat seznam',
selectPermissions_title: 'Vybrat oprávnění', selectPermissions_title: 'Vybrat oprávnění',
selectProject: 'Vybrat projekt', selectProject: 'Vybrat projekt',
settings: 'Nastavení', settings: 'Nastavení',
sortList_title: 'Řadit podle',
stopwatch: 'Časovač', stopwatch: 'Časovač',
subscribeToMyOwnCardsByDefault: 'Ve výchozím nastavení odebírat vlastní karty', subscribeToMyOwnCardsByDefault: 'Ve výchozím nastavení odebírat vlastní karty',
taskActions_title: 'Akce na úkolu', taskActions_title: 'Akce na úkolu',
@@ -199,8 +205,10 @@ export default {
deleteTask: 'Smazat úkol', deleteTask: 'Smazat úkol',
deleteTask_title: 'Smazat úkol', deleteTask_title: 'Smazat úkol',
deleteUser: 'Smazat uživatele', deleteUser: 'Smazat uživatele',
duplicate: 'Duplikovat',
duplicateCard_title: 'Duplikovat kartu',
edit: 'Upravit', edit: 'Upravit',
editDueDate_title: 'Upravit termín do', editDueDate_title: 'Upravit termín',
editDescription_title: 'Upravit popis', editDescription_title: 'Upravit popis',
editEmail_title: 'Upravit e-mail', editEmail_title: 'Upravit e-mail',
editInformation_title: 'Upravit informace', editInformation_title: 'Upravit informace',
@@ -226,13 +234,14 @@ export default {
removeMember: 'Odstranit člena', removeMember: 'Odstranit člena',
save: 'Uložit', save: 'Uložit',
showAllAttachments: 'Zozbrazit všechny přílohy ({{hidden}} skryté)', showAllAttachments: 'Zozbrazit všechny přílohy ({{hidden}} skryté)',
showFewerAttachments: 'Zobrazit méně příloh',
showDetails: 'Zobrazit detaily', showDetails: 'Zobrazit detaily',
showFewerAttachments: 'Zobrazit méně příloh',
sortList_title: 'Řadit podle',
start: 'Start', start: 'Start',
stop: 'Stop', stop: 'Stop',
subscribe: 'Odebírat', subscribe: 'Odebírat',
unsubscribe: 'Neodebírat', unsubscribe: 'Neodebírat',
uploadNewAvatar: 'Nahrát nového avatara', uploadNewAvatar: 'Nahrát nový avatar',
uploadNewImage: 'Nahrát nový obrázek', uploadNewImage: 'Nahrát nový obrázek',
}, },
}, },
+2 -2
View File
@@ -1,8 +1,8 @@
export default { export default {
translation: { translation: {
common: { common: {
emailOrUsername: 'Email nebo uživatelské jméno', emailOrUsername: 'E-mail nebo uživatelské jméno',
invalidEmailOrUsername: 'Nesprávný email nebo uživatelské jméno', invalidEmailOrUsername: 'Nesprávný e-mail nebo uživatelské jméno',
invalidPassword: 'Nesprávné heslo', invalidPassword: 'Nesprávné heslo',
logInToPlanka: 'Přihlásit se do Planka', logInToPlanka: 'Přihlásit se do Planka',
noInternetConnection: 'Bez připojení k internetu', noInternetConnection: 'Bez připojení k internetu',
+11
View File
@@ -51,6 +51,7 @@ export default {
cardNotFound_title: 'Card Not Found', cardNotFound_title: 'Card Not Found',
cardOrActionAreDeleted: 'Card or action are deleted.', cardOrActionAreDeleted: 'Card or action are deleted.',
color: 'Color', color: 'Color',
copy_inline: 'copy',
createBoard_title: 'Create Board', createBoard_title: 'Create Board',
createLabel_title: 'Create Label', createLabel_title: 'Create Label',
createNewOneOrSelectExistingOne: 'Create a new one or select<br />an existing one.', createNewOneOrSelectExistingOne: 'Create a new one or select<br />an existing one.',
@@ -59,6 +60,7 @@ export default {
currentPassword: 'Current password', currentPassword: 'Current password',
dangerZone_title: 'Danger Zone', dangerZone_title: 'Danger Zone',
date: 'Date', date: 'Date',
dueDate: 'Due date',
dueDate_title: 'Due Date', dueDate_title: 'Due Date',
deleteAttachment_title: 'Delete Attachment', deleteAttachment_title: 'Delete Attachment',
deleteBoard_title: 'Delete Board', deleteBoard_title: 'Delete Board',
@@ -107,10 +109,13 @@ export default {
list: 'List', list: 'List',
listActions_title: 'List Actions', listActions_title: 'List Actions',
managers: 'Managers', managers: 'Managers',
managerActions_title: 'Manager Actions',
members: 'Members', members: 'Members',
memberActions_title: 'Member Actions',
minutes: 'Minutes', minutes: 'Minutes',
moveCard_title: 'Move Card', moveCard_title: 'Move Card',
name: 'Name', name: 'Name',
newestFirst: 'Newest first',
newEmail: 'New e-mail', newEmail: 'New e-mail',
newPassword: 'New password', newPassword: 'New password',
newUsername: 'New username', newUsername: 'New username',
@@ -120,6 +125,7 @@ export default {
noProjects: 'No projects', noProjects: 'No projects',
notifications: 'Notifications', notifications: 'Notifications',
noUnreadNotifications: 'No unread notifications.', noUnreadNotifications: 'No unread notifications.',
oldestFirst: 'Oldest first',
openBoard_title: 'Open Board', openBoard_title: 'Open Board',
optional_inline: 'optional', optional_inline: 'optional',
organization: 'Organization', organization: 'Organization',
@@ -134,12 +140,14 @@ export default {
searchLabels: 'Search labels...', searchLabels: 'Search labels...',
searchMembers: 'Search members...', searchMembers: 'Search members...',
searchUsers: 'Search users...', searchUsers: 'Search users...',
searchCards: 'Search cards...',
seconds: 'Seconds', seconds: 'Seconds',
selectBoard: 'Select board', selectBoard: 'Select board',
selectList: 'Select list', selectList: 'Select list',
selectPermissions_title: 'Select Permissions', selectPermissions_title: 'Select Permissions',
selectProject: 'Select project', selectProject: 'Select project',
settings: 'Settings', settings: 'Settings',
sortList_title: 'Sort List',
stopwatch: 'Stopwatch', stopwatch: 'Stopwatch',
subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default', subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default',
taskActions_title: 'Task Actions', taskActions_title: 'Task Actions',
@@ -196,6 +204,8 @@ export default {
deleteTask: 'Delete task', deleteTask: 'Delete task',
deleteTask_title: 'Delete Task', deleteTask_title: 'Delete Task',
deleteUser: 'Delete user', deleteUser: 'Delete user',
duplicate: 'Duplicate',
duplicateCard_title: 'Duplicate Card',
edit: 'Edit', edit: 'Edit',
editDueDate_title: 'Edit Due Date', editDueDate_title: 'Edit Due Date',
editDescription_title: 'Edit Description', editDescription_title: 'Edit Description',
@@ -225,6 +235,7 @@ export default {
showAllAttachments: 'Show all attachments ({{hidden}} hidden)', showAllAttachments: 'Show all attachments ({{hidden}} hidden)',
showDetails: 'Show details', showDetails: 'Show details',
showFewerAttachments: 'Show fewer attachments', showFewerAttachments: 'Show fewer attachments',
sortList_title: 'Sort List',
start: 'Start', start: 'Start',
stop: 'Stop', stop: 'Stop',
subscribe: 'Subscribe', subscribe: 'Subscribe',
+5
View File
@@ -111,6 +111,10 @@ export default {
project: 'Projet', project: 'Projet',
projectNotFound_title: 'Projet introuvable', projectNotFound_title: 'Projet introuvable',
removeMember_title: 'Supprimer le membre', removeMember_title: 'Supprimer le membre',
searchLabels: 'Chercher une étiquette...',
searchMembers: 'Chercher un membre...',
searchUsers: 'Chercher un utilisateur...',
searchCards: 'Chercher une carte...',
seconds: 'Secondes', seconds: 'Secondes',
selectBoard: 'Sélectionner une carte', selectBoard: 'Sélectionner une carte',
selectList: 'Sélectionner une liste', selectList: 'Sélectionner une liste',
@@ -169,6 +173,7 @@ export default {
deleteTask: 'Supprimer la tâche', deleteTask: 'Supprimer la tâche',
deleteTask_title: 'Supprimer la tâche', deleteTask_title: 'Supprimer la tâche',
deleteUser: "Supprimer l'utilisateur", deleteUser: "Supprimer l'utilisateur",
duplicate: 'Dupliquer',
edit: 'Modifier', edit: 'Modifier',
editDueDate_title: "Modifier la date d'échéance", editDueDate_title: "Modifier la date d'échéance",
editDescription_title: 'Éditer la description', editDescription_title: 'Éditer la description',
+231
View File
@@ -0,0 +1,231 @@
import dateFns from 'date-fns/locale/hu';
export default {
dateFns,
format: {
date: 'yyyy.M.d.',
time: 'p',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'MMM. d.',
longDateTime: 'MMMM. d. p',
fullDate: 'y. MMM d.',
fullDateTime: 'y. MMMM d. p',
},
translation: {
common: {
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Az összes változás automatikusan mentésre kerül a kapcsolat helyreállása után.',
areYouSureYouWantToDeleteThisAttachment: 'Biztosan törölni szeretné ezt a mellékletet?',
areYouSureYouWantToDeleteThisBoard: 'Biztosan törölni szeretné ezt a táblát?',
areYouSureYouWantToDeleteThisCard: 'Biztosan törölni szeretné ezt a kártyát?',
areYouSureYouWantToDeleteThisComment: 'Biztosan törölni szeretné ezt a megjegyzést?',
areYouSureYouWantToDeleteThisLabel: 'Biztosan törölni szeretné ezt a címkét?',
areYouSureYouWantToDeleteThisList: 'Biztosan törölni szeretné ezt a listát?',
areYouSureYouWantToDeleteThisProject: 'Biztosan törölni szeretné ezt a projektet?',
areYouSureYouWantToDeleteThisTask: 'Biztosan törölni szeretné ezt a feladatot?',
areYouSureYouWantToDeleteThisUser: 'Biztosan törölni szeretné ezt a felhasználót?',
areYouSureYouWantToLeaveBoard: 'Biztosan el akarja hagyni a táblát?',
areYouSureYouWantToLeaveProject: 'Biztosan el akarja hagyni a projektet?',
areYouSureYouWantToRemoveThisManagerFromProject:
'Biztosan eltávolítja ezt a vezetőt a projektből?',
areYouSureYouWantToRemoveThisMemberFromBoard: 'Biztosan eltávolítja ezt a tagot a tábláról?',
attachment: 'Melléklet',
attachments: 'Mellékletek',
authentication: 'Hitelesítés',
background: 'Háttér',
board: 'Tábla',
boardNotFound_title: 'Tábla nem található',
canComment: 'Hozzászólhat',
canEditContentOfBoard: 'Szerkesztheti a tábla tartalmát.',
canOnlyViewBoard: 'Csak megtekintheti a táblát.',
cardActions_title: 'Kártya műveletek',
cardNotFound_title: 'Kártya nem található',
cardOrActionAreDeleted: 'A kártya vagy művelet törölve lett.',
color: 'Szín',
createBoard_title: 'Tábla létrehozása',
createLabel_title: 'Címke létrehozása',
createNewOneOrSelectExistingOne: 'Hozzon létre egy újat, vagy válasszon ki egy meglévőt.',
createProject_title: 'Projekt létrehozása',
createTextFile_title: 'Szövegfájl létrehozása',
currentPassword: 'Jelenlegi jelszó',
dangerZone_title: 'Veszélyzóna',
date: 'Dátum',
dueDate_title: 'Esedékesség dátuma',
deleteAttachment_title: 'Melléklet törlése',
deleteBoard_title: 'Tábla törlése',
deleteCard_title: 'Kártya törlése',
deleteComment_title: 'Megjegyzés törlése',
deleteLabel_title: 'Címke törlése',
deleteList_title: 'Lista törlése',
deleteProject_title: 'Projekt törlése',
deleteTask_title: 'Feladat törlése',
deleteUser_title: 'Felhasználó törlése',
description: 'Leírás',
detectAutomatically: 'Automatikus érzékelés',
dropFileToUpload: 'Dobja ide a fájlt a feltöltéshez',
editor: 'Szerkesztő',
editAttachment_title: 'Melléklet szerkesztése',
editAvatar_title: 'Avatar szerkesztése',
editBoard_title: 'Tábla szerkesztése',
editDueDate_title: 'Esedékesség dátumának szerkesztése',
editEmail_title: 'E-mail szerkesztése',
editInformation_title: 'Információ szerkesztése',
editLabel_title: 'Címke szerkesztése',
editPassword_title: 'Jelszó szerkesztése',
editPermissions_title: 'Jogosultságok szerkesztése',
editStopwatch_title: 'Stopper szerkesztése',
editUsername_title: 'Felhasználónév szerkesztése',
email: 'E-mail',
emailAlreadyInUse: 'Az e-mail cím már használatban van',
enterCardTitle: 'Adja meg a kártya címét... [Ctrl+Enter] az automatikus megnyitáshoz.',
enterDescription: 'Adja meg a leírást...',
enterFilename: 'Adja meg a fájl nevét',
enterListTitle: 'Adja meg a lista címét...',
enterProjectTitle: 'Adja meg a projekt címét',
enterTaskDescription: 'Adja meg a feladat leírását...',
filterByLabels_title: 'Szűrés címkék alapján',
filterByMembers_title: 'Szűrés tagok alapján',
fromComputer_title: 'Számítógépről',
fromTrello: 'Trello-ról',
general: 'Általános',
hours: 'Órák',
importBoard_title: 'Tábla importálása',
invalidCurrentPassword: 'Érvénytelen jelenlegi jelszó',
labels: 'Címkék',
language: 'Nyelv',
leaveBoard_title: 'Tábla elhagyása',
leaveProject_title: 'Projekt elhagyása',
list: 'Lista',
listActions_title: 'Lista műveletek',
managers: 'Vezetők',
members: 'Tagok',
minutes: 'Percek',
moveCard_title: 'Kártya áthelyezése',
name: 'Név',
newEmail: 'Új e-mail',
newPassword: 'Új jelszó',
newUsername: 'Új felhasználónév',
noConnectionToServer: 'Nincs kapcsolat a szerverrel',
noBoards: 'Nincsenek táblák',
noLists: 'Nincsenek listák',
noProjects: 'Nincsenek projektek',
notifications: 'Értesítések',
noUnreadNotifications: 'Nincsenek olvasatlan értesítések.',
openBoard_title: 'Tábla megnyitása',
optional_inline: 'opcionális',
organization: 'Szervezet',
phone: 'Telefon',
preferences: 'Beállítások',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tipp: nyomja meg a Ctrl-V (Cmd-V a Mac-en) billentyűkombinációt a vágólapról történő melléklet hozzáadásához.',
project: 'Projekt',
projectNotFound_title: 'Projekt nem található',
removeManager_title: 'Vezető eltávolítása',
removeMember_title: 'Tag eltávolítása',
searchLabels: 'Címkék keresése...',
searchMembers: 'Tagok keresése...',
searchUsers: 'Felhasználók keresése...',
seconds: 'Másodpercek',
selectBoard: 'Válassza ki a táblát',
selectList: 'Válassza ki a listát',
selectPermissions_title: 'Jogosultságok kiválasztása',
selectProject: 'Válassza ki a projektet',
settings: 'Beállítások',
stopwatch: 'Stopper',
subscribeToMyOwnCardsByDefault: 'Alapértelmezés szerint feliratkozás a saját kártyáimra',
taskActions_title: 'Feladat műveletek',
tasks: 'Feladatok',
thereIsNoPreviewAvailableForThisAttachment: 'Nincs elérhető előnézet ehhez a melléklethez.',
time: 'Idő',
title: 'Cím',
userActions_title: 'Felhasználói műveletek',
userAddedThisCardToList:
'<0>{{user}}</0><1> hozzáadta ezt a kártyát a következőhöz: {{list}}</1>',
userLeftNewCommentToCard:
'{{user}} új kommentet hagyott itt: «{{comment}}» a következő kártyán: <2>{{card}}</2>',
userMovedCardFromListToList:
'{{user}} áthelyezte ezt a kártyát innen: {{fromList}} ide: {{toList}}',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> áthelyezte ezt a kártyát innen: {{fromList}} ide: {{toList}}</1>',
username: 'Felhasználónév',
usernameAlreadyInUse: 'A felhasználónév már használatban van',
users: 'Felhasználók',
version: 'Verzió',
viewer: 'Néző',
writeComment: 'Írjon egy megjegyzést...',
},
action: {
addAnotherCard: 'Új kártya hozzáadása',
addAnotherList: 'Új lista hozzáadása',
addAnotherTask: 'Új feladat hozzáadása',
addCard: 'Kártya hozzáadása',
addCard_title: 'Kártya hozzáadása',
addComment: 'Megjegyzés hozzáadása',
addList: 'Lista hozzáadása',
addMember: 'Tag hozzáadása',
addMoreDetailedDescription: 'További részletes leírás hozzáadása',
addTask: 'Feladat hozzáadása',
addToCard: 'Kártyához hozzáadása',
addUser: 'Felhasználó hozzáadása',
createBoard: 'Tábla létrehozása',
createFile: 'Fájl létrehozása',
createLabel: 'Címke létrehozása',
createNewLabel: 'Új címke létrehozása',
createProject: 'Projekt létrehozása',
delete: 'Törlés',
deleteAttachment: 'Melléklet törlése',
deleteAvatar: 'Avatar törlése',
deleteBoard: 'Tábla törlése',
deleteCard: 'Kártya törlése',
deleteCard_title: 'Kártya törlése',
deleteComment: 'Megjegyzés törlése',
deleteImage: 'Kép törlése',
deleteLabel: 'Címke törlése',
deleteList: 'Lista törlése',
deleteList_title: 'Lista törlése',
deleteProject: 'Projekt törlése',
deleteProject_title: 'Projekt törlése',
deleteTask: 'Feladat törlése',
deleteTask_title: 'Feladat törlése',
deleteUser: 'Felhasználó törlése',
edit: 'Szerkesztés',
editDueDate_title: 'Esedékesség dátumának szerkesztése',
editDescription_title: 'Leírás szerkesztése',
editEmail_title: 'E-mail szerkesztése',
editInformation_title: 'Információ szerkesztése',
editPassword_title: 'Jelszó szerkesztése',
editPermissions: 'Jogosultságok szerkesztése',
editStopwatch_title: 'Stopper szerkesztése',
editTitle_title: 'Cím szerkesztése',
editUsername_title: 'Felhasználónév szerkesztése',
hideDetails: 'Részletek elrejtése',
import: 'Importálás',
leaveBoard: 'Tábla elhagyása',
leaveProject: 'Projekt elhagyása',
logOut_title: 'Kijelentkezés',
makeCover_title: 'Borító készítése',
move: 'Áthelyezés',
moveCard_title: 'Kártya áthelyezése',
remove: 'Eltávolítás',
removeBackground: 'Háttér eltávolítása',
removeCover_title: 'Borító eltávolítása',
removeFromBoard: 'Eltávolítás a tábláról',
removeFromProject: 'Eltávolítás a projektről',
removeManager: 'Vezető eltávolítása',
removeMember: 'Tag eltávolítása',
save: 'Mentés',
showAllAttachments: 'Összes melléklet megjelenítése ({{hidden}} rejtve)',
showDetails: 'Részletek megjelenítése',
showFewerAttachments: 'Kevesebb melléklet megjelenítése',
start: 'Indítás',
stop: 'Megállítás',
subscribe: 'Feliratkozás',
unsubscribe: 'Leiratkozás',
uploadNewAvatar: 'Új avatar feltöltése',
uploadNewImage: 'Új kép feltöltése',
},
},
};
+8
View File
@@ -0,0 +1,8 @@
import login from './login';
export default {
language: 'hu',
country: 'hu',
name: 'Magyar',
embeddedLocale: login,
};
+22
View File
@@ -0,0 +1,22 @@
export default {
translation: {
common: {
emailOrUsername: 'E-mail vagy felhasználó',
invalidEmailOrUsername: 'Érvénytelen e-mail vagy felhasználó',
invalidPassword: 'Érvénytelen jelszó',
logInToPlanka: 'Plankába belépés',
noInternetConnection: 'Nincs internet kapcsolat',
pageNotFound_title: 'Az oldal nem található',
password: 'Jelszó',
projectManagement: 'Projektmenedzsment',
serverConnectionFailed: 'A szerverkapcsolat sikertelen',
unknownError: 'Ismeretlen hiba, próbáld meg később újra',
useSingleSignOn: 'Egyszeri bejelentkezés használata',
},
action: {
logIn: 'Belépés',
logInWithSSO: 'Belépés SSO-val',
},
},
};
+241
View File
@@ -0,0 +1,241 @@
import dateFns from 'date-fns/locale/id';
export default {
dateFns,
format: {
date: 'dd MMM yyyy',
time: 'p',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'd MMM',
longDateTime: "d MMMM 'pada' p",
fullDate: 'd MMM, y',
fullDateTime: "d MMM, y 'pada' p",
},
translation: {
common: {
aboutPlanka: 'Tentang Planka',
account: 'Akun',
actions: 'Tindakan',
addAttachment_title: 'Tambah Lampiran',
addComment: 'Tambahkan komentar',
addManager_title: 'Tambahkan Manager',
addMember_title: 'Tambahkan Anggota',
addUser_title: 'Tambahkan Pengguna',
administrator: 'Administrator',
all: 'Semua',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Semua perubahan akan disimpan<br />setelah koneksi pulih.',
areYouSureYouWantToDeleteThisAttachment: 'Apakah anda ingin menghapus lampiran ini?',
areYouSureYouWantToDeleteThisBoard: 'Apakah anda ingin menghapus papan ini?',
areYouSureYouWantToDeleteThisCard: 'Apakah anda ingin menghapus kartu ini?',
areYouSureYouWantToDeleteThisComment: 'Apakah anda ingin menghapus komentar ini?',
areYouSureYouWantToDeleteThisLabel: 'Apakah anda ingin menghapus label ini?',
areYouSureYouWantToDeleteThisList: 'Apakah anda ingin menghapus daftar ini?',
areYouSureYouWantToDeleteThisProject: 'Apakah anda ingin menghapus proyek ini?',
areYouSureYouWantToDeleteThisTask: 'Apakah anda ingin menghapus tugas ini?',
areYouSureYouWantToDeleteThisUser: 'Apakah anda ingin menghapus pengguna ini?',
areYouSureYouWantToLeaveBoard: 'Apakah anda ingin keluar dari papan ini?',
areYouSureYouWantToLeaveProject: 'Apakah anda ingin keluar dari proyek ini?',
areYouSureYouWantToRemoveThisManagerFromProject:
'Apakah anda ingin menghapus manajer ini dari papan ini?',
areYouSureYouWantToRemoveThisMemberFromBoard:
'Apakah anda ingin menghapus anggota ini dari papan ini?',
attachment: 'Lampiran',
attachments: 'Lampiran-lampiran',
authentication: 'Autentikasi',
background: 'Latar belakang',
board: 'Papan',
boardNotFound_title: 'Papan Tidak Ditemukan',
canComment: 'Bisa berkomentar',
canEditContentOfBoard: 'Bisa mengubah isi papan.',
canOnlyViewBoard: 'Hanya dapat menglihat isi papan.',
cardActions_title: 'Aksi Kartu',
cardNotFound_title: 'Kartu Tidak Ditemukan',
cardOrActionAreDeleted: 'Kartu atau aksi telah dihapus.',
color: 'Warna',
createBoard_title: 'Buat Papan',
createLabel_title: 'Buat Label',
createNewOneOrSelectExistingOne: 'Create a new one or select<br />an existing one.',
createProject_title: 'Buat Proyek',
createTextFile_title: 'Buat Berkas Teks',
currentPassword: 'Kata sandi sekarang',
dangerZone_title: 'Zona Berbahaya',
date: 'Tanggal',
dueDate_title: 'Tenggat Waktu',
deleteAttachment_title: 'Hapus Lampiran',
deleteBoard_title: 'Hapus Papan',
deleteCard_title: 'Hapus Kartu',
deleteComment_title: 'Hapus Komentar',
deleteLabel_title: 'Hapus Label',
deleteList_title: 'Hapus Daftar',
deleteProject_title: 'Hapus Proyek',
deleteTask_title: 'Hapus Tugas',
deleteUser_title: 'Hapus Pengguna',
description: 'Deskripsi',
detectAutomatically: 'Deteksi otomatis',
dropFileToUpload: 'Tarik berkas untuk menggungah',
editor: 'Pengubah',
editAttachment_title: 'Ubah Lampiran',
editAvatar_title: 'Ubah Avatar',
editBoard_title: 'Ubah Papan',
editDueDate_title: 'Ubah Tenggat Waktu',
editEmail_title: 'Ubah E-mail',
editInformation_title: 'Ubah Informasi',
editLabel_title: 'Ubah Label',
editPassword_title: 'Ubah Kata Sandi',
editPermissions_title: 'Ubah Izin',
editStopwatch_title: 'Ubah Stopwatch',
editUsername_title: 'Ubah Username',
email: 'E-mail',
emailAlreadyInUse: 'E-mail telah digunakan',
enterCardTitle: 'Masukkan judul kartu... [Ctrl+Enter] untuk membuka otomatis.',
enterDescription: 'Masukkan deskripsi...',
enterFilename: 'Masukkan nama berkas...',
enterListTitle: 'Masukkan judul daftar...',
enterProjectTitle: 'Masukkan judul proyek',
enterTaskDescription: 'Masukkan deskripsi tugas...',
filterByLabels_title: 'Saring berdasarkan Label',
filterByMembers_title: 'Saring berdasarkan Anggota',
fromComputer_title: 'Dari Komputer',
fromTrello: 'Dari Trello',
general: 'Umum',
hours: 'Jam',
importBoard_title: 'Impor Papan',
invalidCurrentPassword: 'Kata sandi saat ini tidak valid',
labels: 'Label',
language: 'Bahasa',
leaveBoard_title: 'Keluar dari Papan',
leaveProject_title: 'Keluar dari Proyek',
list: 'Daftar',
listActions_title: 'Aksi Daftar',
managers: 'Manager',
members: 'Anggota',
minutes: 'Menit',
moveCard_title: 'Pindahkan Kartu',
name: 'Nama',
newEmail: 'E-mail baru',
newPassword: 'Kata sandi baru',
newUsername: 'Username baru',
noConnectionToServer: 'Tidak ada koneksi ke server',
noBoards: 'Tidak ada papan',
noLists: 'Tidak ada daftar',
noProjects: 'Tidak ada projek',
notifications: 'Notifikasi',
noUnreadNotifications: 'Tiada notifikasi yang belum dibaca.',
openBoard_title: 'Buka Papan',
optional_inline: 'opsional',
organization: 'Organisasi',
phone: 'Ponsel',
preferences: 'Preferensi',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: tekan Ctrl-V (Cmd-V di Mac) untuk menambahkan lampiran dari papan klip.',
project: 'Proyek',
projectNotFound_title: 'Proyek Tidak Ditemukan',
removeManager_title: 'Hapus Manager',
removeMember_title: 'Hapus Anggota',
searchLabels: 'Cari label...',
searchMembers: 'Cari anggota...',
searchUsers: 'Cari pengguna...',
seconds: 'Detik',
selectBoard: 'Pilih papan',
selectList: 'Pilih daftar',
selectPermissions_title: 'Pilih Izin',
selectProject: 'Pilih proyek',
settings: 'Setelan',
stopwatch: 'Stopwatch',
subscribeToMyOwnCardsByDefault: 'Berlangganan kartu saya sendiri secara default',
taskActions_title: 'Aksi Tugas',
tasks: 'Tugas',
thereIsNoPreviewAvailableForThisAttachment:
'Tidak ada pratinjau yang tersedia untuk lampiran ini.',
time: 'Waktu',
title: 'Judul',
userActions_title: 'Aksi Pengguna',
userAddedThisCardToList: '<0>{{user}}</0><1> menambahkan kartu ini ke {{list}}</1>',
userLeftNewCommentToCard: '{{user}} mengomentari «{{comment}}» di <2>{{card}}</2>',
userMovedCardFromListToList:
'{{user}} memindahkan <2>{{card}}</2> dari {{fromList}} ke {{toList}}',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> memindahkan kartu ini dari {{fromList}} ke {{toList}}</1>',
username: 'Username',
usernameAlreadyInUse: 'Username telah digunakan',
users: 'Pengguna',
version: 'Versi',
viewer: 'Penglihat',
writeComment: 'Tuliskan komentar...',
},
action: {
addAnotherCard: 'Tambahkan kartu lain',
addAnotherList: 'Tambahkan daftar lain',
addAnotherTask: 'Tambahkan aksi lain',
addCard: 'Tambah kartu',
addCard_title: 'Tambah Kartu',
addComment: 'Tambah komentar',
addList: 'Tambah daftar',
addMember: 'Tambah anggota',
addMoreDetailedDescription: 'Tambahkan deskripsi yang lebih detail',
addTask: 'Tambah tugas',
addToCard: 'Tambahkan ke kartu',
addUser: 'Tambah pengguna',
createBoard: 'Tambah papan',
createFile: 'Tambah berkas',
createLabel: 'Tambah label',
createNewLabel: 'Tambah label baru',
createProject: 'Tambah proyek',
delete: 'Hapus',
deleteAttachment: 'Hapus lampiran',
deleteAvatar: 'Hapus avatar',
deleteBoard: 'Hapus papan',
deleteCard: 'Hapus kartu',
deleteCard_title: 'Hapus Kartu',
deleteComment: 'Hapus komentar',
deleteImage: 'Hapus gambar',
deleteLabel: 'Hapus labek',
deleteList: 'Hapus daftar',
deleteList_title: 'Hapus Daftar',
deleteProject: 'Hapus proyek',
deleteProject_title: 'Hapus Proyek',
deleteTask: 'Hapus tugas',
deleteTask_title: 'Hapus Tugas',
deleteUser: 'Hapus pengguna',
edit: 'Ubah',
editDueDate_title: 'Ubah Tenggat Waktu',
editDescription_title: 'Ubdah Deskripsi',
editEmail_title: 'Ubah E-mail',
editInformation_title: 'Ubah Informasi',
editPassword_title: 'Ubah Kata Sandi',
editPermissions: 'Ubah izin',
editStopwatch_title: 'Ubah Stopwatch',
editTitle_title: 'Ubah Judul',
editUsername_title: 'Ubah Username',
hideDetails: 'Sembunyikan detail',
import: 'Impor',
leaveBoard: 'Keluar dari papan',
leaveProject: 'Keluar dari proyek',
logOut_title: 'Keluar',
makeCover_title: 'Buat Cover',
move: 'Pindah',
moveCard_title: 'Pindahkan Kartu',
remove: 'Hapus',
removeBackground: 'Hapus latar belakang',
removeCover_title: 'Hapus Cover',
removeFromBoard: 'Hapus dari papan',
removeFromProject: 'Hapus dari proyek',
removeManager: 'Hapus manager',
removeMember: 'Hapus papan',
save: 'Simpan',
showAllAttachments: 'Tampilkan semua lampiran ({{hidden}} tersembunyi)',
showDetails: 'Tampilkan detail',
showFewerAttachments: 'Tampilkan lampiran lebih sedikit',
start: 'Mulai',
stop: 'Berhenti',
subscribe: 'Berlanggan',
unsubscribe: 'Berhenti berlangganan',
uploadNewAvatar: 'Unggah avatar baru',
uploadNewImage: 'Unggah gambar baru',
},
},
};
+8
View File
@@ -0,0 +1,8 @@
import login from './login';
export default {
language: 'id',
country: 'id',
name: 'Bahasa Indonesia',
embeddedLocale: login,
};
+22
View File
@@ -0,0 +1,22 @@
export default {
translation: {
common: {
emailOrUsername: 'E-mail atau username',
invalidEmailOrUsername: 'E-mail atau username salah',
invalidPassword: 'Kata sandi salah',
logInToPlanka: 'Masuk ke Planka',
noInternetConnection: 'Tidak ada koneksi internet',
pageNotFound_title: 'Halaman Tidak Ditemukan',
password: 'Kata sandi',
projectManagement: 'Manajemen projek',
serverConnectionFailed: 'Koneksi server gagal',
unknownError: 'Kesalahan tidak diketahui, coba lagi nanti.',
useSingleSignOn: 'Gunakan single sign-on',
},
action: {
logIn: 'Masuk',
logInWithSSO: 'Masuk dengan SSO',
},
},
};
+28 -1
View File
@@ -4,10 +4,14 @@ import de from './de';
import en from './en'; import en from './en';
import es from './es'; import es from './es';
import fr from './fr'; import fr from './fr';
import hu from './hu';
import id from './id';
import it from './it'; import it from './it';
import ja from './ja'; import ja from './ja';
import ko from './ko'; import ko from './ko';
import nl from './nl';
import pl from './pl'; import pl from './pl';
import pt from './pt';
import ro from './ro'; import ro from './ro';
import ru from './ru'; import ru from './ru';
import sk from './sk'; import sk from './sk';
@@ -17,7 +21,30 @@ import ua from './ua';
import uz from './uz'; import uz from './uz';
import zh from './zh'; import zh from './zh';
const locales = [cs, da, de, en, es, fr, it, ja, ko, pl, ro, ru, sk, sv, tr, ua, uz, zh]; const locales = [
cs,
da,
de,
en,
es,
fr,
hu,
id,
it,
ja,
ko,
nl,
pl,
pt,
ro,
ru,
sk,
sv,
tr,
ua,
uz,
zh,
];
export default locales; export default locales;
+19 -7
View File
@@ -3,10 +3,10 @@ export default {
date: 'd/M/yyyy', date: 'd/M/yyyy',
time: 'p', time: 'p',
dateTime: '$t(format:date) $t(format:time)', dateTime: '$t(format:date) $t(format:time)',
longDate: 'MMM d', longDate: 'd MMM',
longDateTime: "MMMM d 'at' p", longDateTime: "d MMMM 'alle' p",
fullDate: 'MMM d, y', fullDate: 'd MMM, y',
fullDateTime: "MMMM d, y 'at' p", fullDateTime: "d MMMM, y 'alle' p",
}, },
translation: { translation: {
@@ -21,7 +21,7 @@ export default {
administrator: 'Amministratore', administrator: 'Amministratore',
all: 'tutto', all: 'tutto',
allChangesWillBeAutomaticallySavedAfterConnectionRestored: allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'All changes will be automatically saved<br />after connection restored.', 'Tutte le modifiche verranno salvate<br />al ripristino della connessione.',
areYouSureYouWantToDeleteThisAttachment: 'Sei sicuro di voler eliminare questo allegato?', areYouSureYouWantToDeleteThisAttachment: 'Sei sicuro di voler eliminare questo allegato?',
areYouSureYouWantToDeleteThisBoard: 'Sei sicuro di voler eliminare questa bacheca?', areYouSureYouWantToDeleteThisBoard: 'Sei sicuro di voler eliminare questa bacheca?',
areYouSureYouWantToDeleteThisCard: 'Sei sicuro di voler eliminare questa card?', areYouSureYouWantToDeleteThisCard: 'Sei sicuro di voler eliminare questa card?',
@@ -50,6 +50,7 @@ export default {
cardNotFound_title: 'Card non trovata', cardNotFound_title: 'Card non trovata',
cardOrActionAreDeleted: "La card o l'azione vengono eliminate.", cardOrActionAreDeleted: "La card o l'azione vengono eliminate.",
color: 'Colore', color: 'Colore',
copy_inline: 'copia',
createBoard_title: 'Crea Bacheca', createBoard_title: 'Crea Bacheca',
createLabel_title: 'Crea Etichetta', createLabel_title: 'Crea Etichetta',
createNewOneOrSelectExistingOne: 'Crea nuovo o seleziona<br />esistente.', createNewOneOrSelectExistingOne: 'Crea nuovo o seleziona<br />esistente.',
@@ -58,6 +59,7 @@ export default {
currentPassword: 'Password corrente', currentPassword: 'Password corrente',
dangerZone_title: 'Zona pericolosa', dangerZone_title: 'Zona pericolosa',
date: 'Data', date: 'Data',
dueDate: 'Data di scadenza',
dueDate_title: 'Data di scadenza', dueDate_title: 'Data di scadenza',
deleteAttachment_title: 'Elimina Allegato', deleteAttachment_title: 'Elimina Allegato',
deleteBoard_title: 'Elimina Bacheca', deleteBoard_title: 'Elimina Bacheca',
@@ -94,8 +96,10 @@ export default {
filterByLabels_title: 'Filtra per Etichetta', filterByLabels_title: 'Filtra per Etichetta',
filterByMembers_title: 'Filtra per Membro', filterByMembers_title: 'Filtra per Membro',
fromComputer_title: 'Dal Computer', fromComputer_title: 'Dal Computer',
fromTrello: 'Da Trello',
general: 'Generale', general: 'Generale',
hours: 'Ore', hours: 'Ore',
importBoard_title: 'Importa Board',
invalidCurrentPassword: 'Password corrente non valida', invalidCurrentPassword: 'Password corrente non valida',
labels: 'Etichette', labels: 'Etichette',
language: 'Lingua', language: 'Lingua',
@@ -108,6 +112,7 @@ export default {
minutes: 'Minuti', minutes: 'Minuti',
moveCard_title: 'Sposta card', moveCard_title: 'Sposta card',
name: 'Nome', name: 'Nome',
newestFirst: 'Dal più recente',
newEmail: 'Nuova e-mail', newEmail: 'Nuova e-mail',
newPassword: 'Nuova password', newPassword: 'Nuova password',
newUsername: 'Nuovo username', newUsername: 'Nuovo username',
@@ -117,13 +122,14 @@ export default {
noProjects: 'No progetti', noProjects: 'No progetti',
notifications: 'Notifiche', notifications: 'Notifiche',
noUnreadNotifications: 'Nessuna notifica non letta.', noUnreadNotifications: 'Nessuna notifica non letta.',
oldestFirst: 'Dal meno recente',
openBoard_title: 'Bacheca aperta', openBoard_title: 'Bacheca aperta',
optional_inline: 'opzionale', optional_inline: 'opzionale',
organization: 'Organizazzione', organization: 'Organizazzione',
phone: 'Telefono', phone: 'Telefono',
preferences: 'Preferenze', preferences: 'Preferenze',
pressPasteShortcutToAddAttachmentFromClipboard: pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: prmi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard.', 'Consiglio: premi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard.',
project: 'Progetto', project: 'Progetto',
projectNotFound_title: 'Progetto non trovato', projectNotFound_title: 'Progetto non trovato',
removeManager_title: 'Rimuovi Manager', removeManager_title: 'Rimuovi Manager',
@@ -131,12 +137,14 @@ export default {
searchLabels: 'Cerca etichette...', searchLabels: 'Cerca etichette...',
searchMembers: 'Cerca membri...', searchMembers: 'Cerca membri...',
searchUsers: 'Cerca utenti...', searchUsers: 'Cerca utenti...',
searchCards: 'Cerca cards...',
seconds: 'Secondi', seconds: 'Secondi',
selectBoard: 'Seleziona bacheca', selectBoard: 'Seleziona bacheca',
selectList: 'Seleziona lista', selectList: 'Seleziona lista',
selectPermissions_title: 'Seleziona Permessi', selectPermissions_title: 'Seleziona Permessi',
selectProject: 'Seleziona progetto', selectProject: 'Seleziona progetto',
settings: 'Impostazioni', settings: 'Impostazioni',
sortList_title: 'Ordina',
stopwatch: 'Timer', stopwatch: 'Timer',
subscribeToMyOwnCardsByDefault: 'Abbonati alle mie card per impostazione predefinita', subscribeToMyOwnCardsByDefault: 'Abbonati alle mie card per impostazione predefinita',
taskActions_title: 'Task Azioni', taskActions_title: 'Task Azioni',
@@ -193,6 +201,8 @@ export default {
deleteTask: 'Elimina task', deleteTask: 'Elimina task',
deleteTask_title: 'Elimina Task', deleteTask_title: 'Elimina Task',
deleteUser: 'Elimina utente', deleteUser: 'Elimina utente',
duplicate: 'Duplica',
duplicateCard_title: 'Duplica Card',
edit: 'Modifica', edit: 'Modifica',
editDueDate_title: 'Modifica data di scadenza', editDueDate_title: 'Modifica data di scadenza',
editDescription_title: 'Modifica Descrizione', editDescription_title: 'Modifica Descrizione',
@@ -204,9 +214,10 @@ export default {
editTitle_title: 'Modifica Titolo', editTitle_title: 'Modifica Titolo',
editUsername_title: 'Modifica Username', editUsername_title: 'Modifica Username',
hideDetails: 'Nascondi dettagli', hideDetails: 'Nascondi dettagli',
import: 'Importa',
leaveBoard: 'Lascia bacheca', leaveBoard: 'Lascia bacheca',
leaveProject: 'Lascia progetto', leaveProject: 'Lascia progetto',
logOut_title: 'Log Out', logOut_title: 'Disconnettiti',
makeCover_title: 'Crea Cover', makeCover_title: 'Crea Cover',
move: 'Muovi', move: 'Muovi',
moveCard_title: 'Muovi Card', moveCard_title: 'Muovi Card',
@@ -221,6 +232,7 @@ export default {
showAllAttachments: 'Mostra tutti gli allegati ({{hidden}} hidden)', showAllAttachments: 'Mostra tutti gli allegati ({{hidden}} hidden)',
showDetails: 'Mostra dettagli', showDetails: 'Mostra dettagli',
showFewerAttachments: 'Mostra meno allegati', showFewerAttachments: 'Mostra meno allegati',
sortList_title: 'Ordina',
start: 'Inizio', start: 'Inizio',
stop: 'Ferma', stop: 'Ferma',
subscribe: 'Iscriviti', subscribe: 'Iscriviti',
+242
View File
@@ -0,0 +1,242 @@
import dateFns from 'date-fns/locale/nl';
export default {
dateFns,
format: {
date: 'd-M-yyyy',
time: 'p',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'd MMM',
longDateTime: "d MMMM 'om' p",
fullDate: 'd MMM y',
fullDateTime: "d MMMM y 'om' p",
},
translation: {
common: {
aboutPlanka: 'Over Planka',
account: 'Account',
actions: 'Acties',
addAttachment_title: 'Bijlage toevoegen',
addComment: 'Opmerking toevoegen',
addManager_title: 'Manager toevoegen',
addMember_title: 'Lid toevoegen',
addUser_title: 'Gebruiker toevoegen',
administrator: 'Beheerder',
all: 'Alle',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Alle wijzigingen worden automatisch opgeslagen<br />nadat de verbinding is hersteld.',
areYouSureYouWantToDeleteThisAttachment: 'Weet u zeker dat u deze bijlage wilt verwijderen?',
areYouSureYouWantToDeleteThisBoard: 'Weet u zeker dat u dit bord wilt verwijderen?',
areYouSureYouWantToDeleteThisCard: 'Weet u zeker dat u deze kaart wilt verwijderen?',
areYouSureYouWantToDeleteThisComment: 'Weet u zeker dat u deze opmerking wilt verwijderen?',
areYouSureYouWantToDeleteThisLabel: 'Weet u zeker dat u dit label wilt verwijderen?',
areYouSureYouWantToDeleteThisList: 'Weet u zeker dat u deze lijst wilt verwijderen?',
areYouSureYouWantToDeleteThisProject: 'Weet u zeker dat u dit project wilt verwijderen?',
areYouSureYouWantToDeleteThisTask: 'Weet u zeker dat u deze taak wilt verwijderen?',
areYouSureYouWantToDeleteThisUser: 'Weet u zeker dat u deze gebruiker wilt verwijderen?',
areYouSureYouWantToLeaveBoard: 'Weet u zeker dat u het bord wilt verlaten?',
areYouSureYouWantToLeaveProject: 'Weet u zeker dat u het project wilt verlaten?',
areYouSureYouWantToRemoveThisManagerFromProject:
'Weet u zeker dat u deze manager uit het project wilt verwijderen?',
areYouSureYouWantToRemoveThisMemberFromBoard:
'Weet u zeker dat u dit lid uit het bord wilt verwijderen?',
attachment: 'Bijlage',
attachments: 'Bijlagen',
authentication: 'Authenticatie',
background: 'Achtergrond',
board: 'Bord',
boardNotFound_title: 'Bord niet gevonden',
canComment: 'Kan opmerking plaatsen',
canEditContentOfBoard: 'Kan de inhoud van het bord bewerken.',
canOnlyViewBoard: 'Kan alleen het bord bekijken.',
cardActions_title: 'Kaartacties',
cardNotFound_title: 'Kaart niet gevonden',
cardOrActionAreDeleted: 'Kaart of actie zijn verwijderd.',
color: 'Kleur',
createBoard_title: 'Bord aanmaken',
createLabel_title: 'Label aanmaken',
createNewOneOrSelectExistingOne: 'Maak een nieuwe of selecteer een bestaande.',
createProject_title: 'Project aanmaken',
createTextFile_title: 'Tekstbestand aanmaken',
currentPassword: 'Huidig wachtwoord',
dangerZone_title: 'Gevaarlijke zone',
date: 'Datum',
dueDate_title: 'Vervaldatum',
deleteAttachment_title: 'Bijlage verwijderen',
deleteBoard_title: 'Bord verwijderen',
deleteCard_title: 'Kaart verwijderen',
deleteComment_title: 'Opmerking verwijderen',
deleteLabel_title: 'Label verwijderen',
deleteList_title: 'Lijst verwijderen',
deleteProject_title: 'Project verwijderen',
deleteTask_title: 'Taak verwijderen',
deleteUser_title: 'Gebruiker verwijderen',
description: 'Beschrijving',
detectAutomatically: 'Automatisch detecteren',
dropFileToUpload: 'Sleep bestand om te uploaden',
editor: 'Editor',
editAttachment_title: 'Bijlage bewerken',
editAvatar_title: 'Avatar bewerken',
editBoard_title: 'Bord bewerken',
editDueDate_title: 'Vervaldatum bewerken',
editEmail_title: 'E-mail bewerken',
editInformation_title: 'Informatie bewerken',
editLabel_title: 'Label bewerken',
editPassword_title: 'Wachtwoord bewerken',
editPermissions_title: 'Machtigingen bewerken',
editStopwatch_title: 'Stopwatch bewerken',
editUsername_title: 'Gebruikersnaam bewerken',
email: 'E-mail',
emailAlreadyInUse: 'E-mail is al in gebruik',
enterCardTitle: 'Voer kaarttitel in... [Ctrl+Enter] om automatisch te openen.',
enterDescription: 'Beschrijving invoeren...',
enterFilename: 'Bestandsnaam invoeren',
enterListTitle: 'Voer lijsttitel in...',
enterProjectTitle: 'Voer projecttitel in',
enterTaskDescription: 'Taakbeschrijving invoeren...',
filterByLabels_title: 'Filteren op labels',
filterByMembers_title: 'Filteren op leden',
fromComputer_title: 'Van computer',
fromTrello: 'Van Trello',
general: 'Algemeen',
hours: 'Uren',
importBoard_title: 'Bord importeren',
invalidCurrentPassword: 'Ongeldig huidig wachtwoord',
labels: 'Labels',
language: 'Taal',
leaveBoard_title: 'Bord verlaten',
leaveProject_title: 'Project verlaten',
list: 'Lijst',
listActions_title: 'Lijstacties',
managers: 'Managers',
members: 'Leden',
minutes: 'Minuten',
moveCard_title: 'Kaart verplaatsen',
name: 'Naam',
newEmail: 'Nieuwe e-mail',
newPassword: 'Nieuw wachtwoord',
newUsername: 'Nieuwe gebruikersnaam',
noConnectionToServer: 'Geen verbinding met server',
noBoards: 'Geen borden',
noLists: 'Geen lijsten',
noProjects: 'Geen projecten',
notifications: 'Meldingen',
noUnreadNotifications: 'Geen ongelezen meldingen.',
openBoard_title: 'Bord openen',
optional_inline: 'optioneel',
organization: 'Organisatie',
phone: 'Telefoon',
preferences: 'Voorkeuren',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: druk op Ctrl-V (Cmd-V op Mac) om een bijlage van het klembord toe te voegen.',
project: 'Project',
projectNotFound_title: 'Project niet gevonden',
removeManager_title: 'Manager verwijderen',
removeMember_title: 'Lid verwijderen',
searchLabels: 'Labels zoeken...',
searchMembers: 'Leden zoeken...',
searchUsers: 'Gebruikers zoeken...',
seconds: 'Seconden',
selectBoard: 'Bord selecteren',
selectList: 'Lijst selecteren',
selectPermissions_title: 'Machtigingen selecteren',
selectProject: 'Project selecteren',
settings: 'Instellingen',
stopwatch: 'Stopwatch',
subscribeToMyOwnCardsByDefault: 'Standaard abonneren op mijn eigen kaarten',
taskActions_title: 'Takenacties',
tasks: 'Taken',
thereIsNoPreviewAvailableForThisAttachment:
'Er is geen voorbeeld beschikbaar voor deze bijlage.',
time: 'Tijd',
title: 'Titel',
userActions_title: 'Gebruikersacties',
userAddedThisCardToList: '<0>{{user}}</0><1> heeft deze kaart toegevoegd aan {{list}}</1>',
userLeftNewCommentToCard:
'{{user}} heeft een nieuwe opmerking achtergelaten «{{comment}}» bij <2>{{card}}</2>',
userMovedCardFromListToList:
'{{user}} heeft <2>{{card}}</2> verplaatst van {{fromList}} naar {{toList}}',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> heeft deze kaart verplaatst van {{fromList}} naar {{toList}}</1>',
username: 'Gebruikersnaam',
usernameAlreadyInUse: 'Gebruikersnaam is al in gebruik',
users: 'Gebruikers',
version: 'Versie',
viewer: 'Kijker',
writeComment: 'Schrijf een opmerking...',
},
action: {
addAnotherCard: 'Voeg nog een kaart toe',
addAnotherList: 'Voeg nog een lijst toe',
addAnotherTask: 'Voeg nog een taak toe',
addCard: 'Kaart toevoegen',
addCard_title: 'Kaart toevoegen',
addComment: 'Opmerking toevoegen',
addList: 'Lijst toevoegen',
addMember: 'Lid toevoegen',
addMoreDetailedDescription: 'Meer gedetailleerde beschrijving toevoegen',
addTask: 'Taak toevoegen',
addToCard: 'Toevoegen aan kaart',
addUser: 'Gebruiker toevoegen',
createBoard: 'Bord aanmaken',
createFile: 'Bestand aanmaken',
createLabel: 'Label aanmaken',
createNewLabel: 'Nieuw label aanmaken',
createProject: 'Project aanmaken',
delete: 'Verwijderen',
deleteAttachment: 'Bijlage verwijderen',
deleteAvatar: 'Avatar verwijderen',
deleteBoard: 'Bord verwijderen',
deleteCard: 'Kaart verwijderen',
deleteCard_title: 'Kaart verwijderen',
deleteComment: 'Opmerking verwijderen',
deleteImage: 'Afbeelding verwijderen',
deleteLabel: 'Label verwijderen',
deleteList: 'Lijst verwijderen',
deleteList_title: 'Lijst verwijderen',
deleteProject: 'Project verwijderen',
deleteProject_title: 'Project verwijderen',
deleteTask: 'Taak verwijderen',
deleteTask_title: 'Taak verwijderen',
deleteUser: 'Gebruiker verwijderen',
edit: 'Bewerken',
editDueDate_title: 'Vervaldatum bewerken',
editDescription_title: 'Beschrijving bewerken',
editEmail_title: 'E-mail bewerken',
editInformation_title: 'Informatie bewerken',
editPassword_title: 'Wachtwoord bewerken',
editPermissions: 'Machtigingen bewerken',
editStopwatch_title: 'Stopwatch bewerken',
editTitle_title: 'Titel bewerken',
editUsername_title: 'Gebruikersnaam bewerken',
hideDetails: 'Details verbergen',
import: 'Importeren',
leaveBoard: 'Bord verlaten',
leaveProject: 'Project verlaten',
logOut_title: 'Uitloggen',
makeCover_title: 'Omslag maken',
move: 'Verplaatsen',
moveCard_title: 'Kaart verplaatsen',
remove: 'Verwijderen',
removeBackground: 'Achtergrond verwijderen',
removeCover_title: 'Omslag verwijderen',
removeFromBoard: 'Verwijderen van bord',
removeFromProject: 'Verwijderen van project',
removeManager: 'Manager verwijderen',
removeMember: 'Lid verwijderen',
save: 'Opslaan',
showAllAttachments: 'Alle bijlagen weergeven ({{hidden}} verbergen)',
showDetails: 'Details weergeven',
showFewerAttachments: 'Minder bijlagen weergeven',
start: 'Start',
stop: 'Stop',
subscribe: 'Abonneren',
unsubscribe: 'Afmelden',
uploadNewAvatar: 'Nieuwe avatar uploaden',
uploadNewImage: 'Nieuwe afbeelding uploaden',
},
},
};
+8
View File
@@ -0,0 +1,8 @@
import login from './login';
export default {
language: 'nl',
country: 'nl',
name: 'Nederlands',
embeddedLocale: login,
};
+22
View File
@@ -0,0 +1,22 @@
export default {
translation: {
common: {
emailOrUsername: 'E-mail of gebruikersnaam',
invalidEmailOrUsername: 'Ongeldig e-mailadres of gebruikersnaam',
invalidPassword: 'Ongeldig wachtwoord',
logInToPlanka: 'Inloggen bij Planka',
noInternetConnection: 'Geen internetverbinding',
pageNotFound_title: 'Pagina niet gevonden',
password: 'Wachtwoord',
projectManagement: 'Projectbeheer',
serverConnectionFailed: 'Verbinding met de server mislukt',
unknownError: 'Onbekende fout, probeer het later opnieuw',
useSingleSignOn: 'Gebruik single sign-on',
},
action: {
logIn: 'Inloggen',
logInWithSSO: 'Inloggen met SSO',
},
},
};
+241
View File
@@ -0,0 +1,241 @@
import dateFns from 'date-fns/locale/pt-BR';
export default {
dateFns,
format: {
date: 'dd/MM/yyyy',
time: 'p',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'd MMM',
longDateTime: "d 'de' MMMM 'às' p",
fullDate: 'd MMM, y',
fullDateTime: "d 'de' MMMM, y 'às' p",
},
translation: {
common: {
aboutPlanka: 'Sobre o Planka',
account: 'Conta',
actions: 'Ações',
addAttachment_title: 'Adicionar Anexo',
addComment: 'Adicionar comentário',
addManager_title: 'Adicionar Gerente',
addMember_title: 'Adicionar Membro',
addUser_title: 'Adicionar Usuário',
administrator: 'Administrador',
all: 'Todos',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Todas as alterações serão salvas automaticamente<br />após a conexão ser restaurada.',
areYouSureYouWantToDeleteThisAttachment: 'Tem certeza de que deseja excluir este anexo?',
areYouSureYouWantToDeleteThisBoard: 'Tem certeza de que deseja excluir este quadro?',
areYouSureYouWantToDeleteThisCard: 'Tem certeza de que deseja excluir este cartão?',
areYouSureYouWantToDeleteThisComment: 'Tem certeza de que deseja excluir este comentário?',
areYouSureYouWantToDeleteThisLabel: 'Tem certeza de que deseja excluir este rótulo?',
areYouSureYouWantToDeleteThisList: 'Tem certeza de que deseja excluir esta lista?',
areYouSureYouWantToDeleteThisProject: 'Tem certeza de que deseja excluir este projeto?',
areYouSureYouWantToDeleteThisTask: 'Tem certeza de que deseja excluir esta tarefa?',
areYouSureYouWantToDeleteThisUser: 'Tem certeza de que deseja excluir este usuário?',
areYouSureYouWantToLeaveBoard: 'Tem certeza de que deseja sair do quadro?',
areYouSureYouWantToLeaveProject: 'Tem certeza de que deseja sair do projeto?',
areYouSureYouWantToRemoveThisManagerFromProject:
'Tem certeza de que deseja remover este gerente do projeto?',
areYouSureYouWantToRemoveThisMemberFromBoard:
'Tem certeza de que deseja remover este membro do quadro?',
attachment: 'Anexo',
attachments: 'Anexos',
authentication: 'Autenticação',
background: 'Fundo',
board: 'Quadro',
boardNotFound_title: 'Quadro não encontrado',
canComment: 'Pode comentar',
canEditContentOfBoard: 'Pode editar o conteúdo do quadro.',
canOnlyViewBoard: 'Só pode visualizar o quadro.',
cardActions_title: 'Ações do Cartão',
cardNotFound_title: 'Cartão não encontrado',
cardOrActionAreDeleted: 'Cartão ou ação foram excluídos.',
color: 'Cor',
createBoard_title: 'Criar Quadro',
createLabel_title: 'Criar Rótulo',
createNewOneOrSelectExistingOne: 'Criar um novo ou selecionar<br />um existente.',
createProject_title: 'Criar Projeto',
createTextFile_title: 'Criar Arquivo de Texto',
currentPassword: 'Senha atual',
dangerZone_title: 'Zona de Perigo',
date: 'Data',
dueDate_title: 'Data de Vencimento',
deleteAttachment_title: 'Excluir Anexo',
deleteBoard_title: 'Excluir Quadro',
deleteCard_title: 'Excluir Cartão',
deleteComment_title: 'Excluir Comentário',
deleteLabel_title: 'Excluir Rótulo',
deleteList_title: 'Excluir Lista',
deleteProject_title: 'Excluir Projeto',
deleteTask_title: 'Excluir Tarefa',
deleteUser_title: 'Excluir Usuário',
description: 'Descrição',
detectAutomatically: 'Detectar automaticamente',
dropFileToUpload: 'Solte o arquivo para enviar',
editor: 'Editor',
editAttachment_title: 'Editar Anexo',
editAvatar_title: 'Editar Avatar',
editBoard_title: 'Editar Quadro',
editDueDate_title: 'Editar Data de Vencimento',
editEmail_title: 'Editar E-mail',
editInformation_title: 'Editar Informações',
editLabel_title: 'Editar Rótulo',
editPassword_title: 'Editar Senha',
editPermissions_title: 'Editar Permissões',
editStopwatch_title: 'Editar Cronômetro',
editUsername_title: 'Editar Nome de Usuário',
email: 'E-mail',
emailAlreadyInUse: 'E-mail já está em uso',
enterCardTitle: 'Digite o título do cartão... [Ctrl+Enter] para abrir automaticamente.',
enterDescription: 'Digite a descrição...',
enterFilename: 'Digite o nome do arquivo',
enterListTitle: 'Digite o título da lista...',
enterProjectTitle: 'Digite o título do projeto',
enterTaskDescription: 'Digite a descrição da tarefa...',
filterByLabels_title: 'Filtrar por Rótulos',
filterByMembers_title: 'Filtrar por Membros',
fromComputer_title: 'Do Computador',
fromTrello: 'Do Trello',
general: 'Geral',
hours: 'Horas',
importBoard_title: 'Importar Quadro',
invalidCurrentPassword: 'Senha atual inválida',
labels: 'Rótulos',
language: 'Idioma',
leaveBoard_title: 'Sair do Quadro',
leaveProject_title: 'Sair do Projeto',
list: 'Lista',
listActions_title: 'Ações da Lista',
managers: 'Gerentes',
members: 'Membros',
minutes: 'Minutos',
moveCard_title: 'Mover Cartão',
name: 'Nome',
newEmail: 'Novo e-mail',
newPassword: 'Nova senha',
newUsername: 'Novo nome de usuário',
noConnectionToServer: 'Sem conexão com o servidor',
noBoards: 'Sem quadros',
noLists: 'Sem listas',
noProjects: 'Sem projetos',
notifications: 'Notificações',
noUnreadNotifications: 'Nenhuma notificação não lida.',
openBoard_title: 'Abrir Quadro',
optional_inline: 'opcional',
organization: 'Organização',
phone: 'Telefone',
preferences: 'Preferências',
pressPasteShortcutToAddAttachmentFromClipboard:
'Dica: pressione Ctrl-V (Cmd-V no Mac) para adicionar um anexo da área de transferência.',
project: 'Projeto',
projectNotFound_title: 'Projeto não encontrado',
removeManager_title: 'Remover Gerente',
removeMember_title: 'Remover Membro',
searchLabels: 'Pesquisar rótulos...',
searchMembers: 'Pesquisar membros...',
searchUsers: 'Pesquisar usuários...',
seconds: 'Segundos',
selectBoard: 'Selecionar quadro',
selectList: 'Selecionar lista',
selectPermissions_title: 'Selecionar Permissões',
selectProject: 'Selecionar projeto',
settings: 'Configurações',
stopwatch: 'Cronômetro',
subscribeToMyOwnCardsByDefault: 'Inscrever-se automaticamente em meus próprios cartões',
taskActions_title: 'Ações da Tarefa',
tasks: 'Tarefas',
thereIsNoPreviewAvailableForThisAttachment:
'Não há pré-visualização disponível para este anexo.',
time: 'Tempo',
title: 'Título',
userActions_title: 'Ações do Usuário',
userAddedThisCardToList: '<0>{{user}}</0><1> adicionou este cartão a {{list}}</1>',
userLeftNewCommentToCard:
'{{user}} deixou um novo comentário «{{comment}}» em <2>{{card}}</2>',
userMovedCardFromListToList: '{{user}} moveu <2>{{card}}</2> de {{fromList}} para {{toList}}',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> moveu este cartão de {{fromList}} para {{toList}}</1>',
username: 'Nome de usuário',
usernameAlreadyInUse: 'Nome de usuário já está em uso',
users: 'Usuários',
version: 'Versão',
viewer: 'Visualizador',
writeComment: 'Escreva um comentário...',
},
action: {
addAnotherCard: 'Adicionar outro cartão',
addAnotherList: 'Adicionar outra lista',
addAnotherTask: 'Adicionar outra tarefa',
addCard: 'Adicionar cartão',
addCard_title: 'Adicionar Cartão',
addComment: 'Adicionar comentário',
addList: 'Adicionar lista',
addMember: 'Adicionar membro',
addMoreDetailedDescription: 'Adicionar descrição mais detalhada',
addTask: 'Adicionar tarefa',
addToCard: 'Adicionar ao cartão',
addUser: 'Adicionar usuário',
createBoard: 'Criar quadro',
createFile: 'Criar arquivo',
createLabel: 'Criar rótulo',
createNewLabel: 'Criar novo rótulo',
createProject: 'Criar projeto',
delete: 'Excluir',
deleteAttachment: 'Excluir anexo',
deleteAvatar: 'Excluir avatar',
deleteBoard: 'Excluir quadro',
deleteCard: 'Excluir cartão',
deleteCard_title: 'Excluir Cartão',
deleteComment: 'Excluir comentário',
deleteImage: 'Excluir imagem',
deleteLabel: 'Excluir rótulo',
deleteList: 'Excluir lista',
deleteList_title: 'Excluir Lista',
deleteProject: 'Excluir projeto',
deleteProject_title: 'Excluir Projeto',
deleteTask: 'Excluir tarefa',
deleteTask_title: 'Excluir Tarefa',
deleteUser: 'Excluir usuário',
edit: 'Editar',
editDueDate_title: 'Editar Data de Vencimento',
editDescription_title: 'Editar Descrição',
editEmail_title: 'Editar E-mail',
editInformation_title: 'Editar Informações',
editPassword_title: 'Editar Senha',
editPermissions: 'Editar permissões',
editStopwatch_title: 'Editar Cronômetro',
editTitle_title: 'Editar Título',
editUsername_title: 'Editar Nome de Usuário',
hideDetails: 'Ocultar detalhes',
import: 'Importar',
leaveBoard: 'Sair do quadro',
leaveProject: 'Sair do projeto',
logOut_title: 'Sair',
makeCover_title: 'Tornar Capa',
move: 'Mover',
moveCard_title: 'Mover Cartão',
remove: 'Remover',
removeBackground: 'Remover fundo',
removeCover_title: 'Remover Capa',
removeFromBoard: 'Remover do quadro',
removeFromProject: 'Remover do projeto',
removeManager: 'Remover gerente',
removeMember: 'Remover membro',
save: 'Salvar',
showAllAttachments: 'Mostrar todos os anexos ({{hidden}} ocultos)',
showDetails: 'Mostrar detalhes',
showFewerAttachments: 'Mostrar menos anexos',
start: 'Iniciar',
stop: 'Parar',
subscribe: 'Inscrever-se',
unsubscribe: 'Cancelar inscrição',
uploadNewAvatar: 'Enviar novo avatar',
uploadNewImage: 'Enviar nova imagem',
},
},
};
+8
View File
@@ -0,0 +1,8 @@
import login from './login';
export default {
language: 'pt',
country: 'br',
name: 'Português',
embeddedLocale: login,
};
+22
View File
@@ -0,0 +1,22 @@
export default {
translation: {
common: {
emailOrUsername: 'E-mail ou nome de usuário',
invalidEmailOrUsername: 'E-mail ou nome de usuário inválido',
invalidPassword: 'Senha inválida',
logInToPlanka: 'Entrar no Planka',
noInternetConnection: 'Sem conexão com a internet',
pageNotFound_title: 'Página não encontrada',
password: 'Senha',
projectManagement: 'Gerenciamento de projetos',
serverConnectionFailed: 'Falha na conexão com o servidor',
unknownError: 'Erro desconhecido, tente novamente mais tarde',
useSingleSignOn: 'Usar login único',
},
action: {
logIn: 'Entrar',
logInWithSSO: 'Entrar com SSO',
},
},
};
+2
View File
@@ -199,6 +199,8 @@ export default {
deleteProject: 'Удалить проект', deleteProject: 'Удалить проект',
deleteTask: 'Удалить задачу', deleteTask: 'Удалить задачу',
deleteUser: 'Удалить пользователя', deleteUser: 'Удалить пользователя',
duplicate: 'Дублировать',
duplicateCard_title: 'Дублировать карточку',
edit: 'Изменить', edit: 'Изменить',
editBackground: 'Изменить фон', editBackground: 'Изменить фон',
editDueDate: 'Изменить срок', editDueDate: 'Изменить срок',
+1 -1
View File
@@ -67,7 +67,7 @@ export default {
deleteTask_title: '删除任务', deleteTask_title: '删除任务',
deleteUser_title: '删除用户', deleteUser_title: '删除用户',
description: '描述', description: '描述',
detectAutomatically: '自动删除', detectAutomatically: '自动检测',
dropFileToUpload: '拖放文件以上传', dropFileToUpload: '拖放文件以上传',
editor: '编辑器', editor: '编辑器',
editAttachment_title: '编辑附件', editAttachment_title: '编辑附件',
+47
View File
@@ -3,6 +3,9 @@ import { attr, fk, many } from 'redux-orm';
import BaseModel from './BaseModel'; import BaseModel from './BaseModel';
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
import User from './User';
import Label from './Label';
export default class extends BaseModel { export default class extends BaseModel {
static modelName = 'Board'; static modelName = 'Board';
@@ -25,6 +28,9 @@ export default class extends BaseModel {
}), }),
filterUsers: many('User', 'filterBoards'), filterUsers: many('User', 'filterBoards'),
filterLabels: many('Label', 'filterBoards'), filterLabels: many('Label', 'filterBoards'),
filterText: attr({
getDefault: () => '',
}),
}; };
static reducer({ type, payload }, Board) { static reducer({ type, payload }, Board) {
@@ -167,6 +173,47 @@ export default class extends BaseModel {
Board.withId(payload.boardId).filterLabels.remove(payload.id); Board.withId(payload.boardId).filterLabels.remove(payload.id);
break; break;
case ActionTypes.TEXT_FILTER_IN_CURRENT_BOARD: {
const board = Board.withId(payload.boardId);
let filterText = payload.text;
const posSpace = filterText.indexOf(' ');
// Shortcut to user filters
const posAT = filterText.indexOf('@');
if (posAT >= 0 && posSpace > 0 && posAT < posSpace) {
const userId = User.findUsersFromText(
filterText.substring(posAT + 1, posSpace),
board.memberships.toModelArray().map((membership) => membership.user),
);
if (
userId &&
board.filterUsers.toModelArray().filter((user) => user.id === userId).length === 0
) {
board.filterUsers.add(userId);
filterText = filterText.substring(0, posAT);
}
}
// Shortcut to label filters
const posSharp = filterText.indexOf('#');
if (posSharp >= 0 && posSpace > 0 && posSharp < posSpace) {
const labelId = Label.findLabelsFromText(
filterText.substring(posSharp + 1, posSpace),
board.labels.toModelArray(),
);
if (
labelId &&
board.filterLabels.toModelArray().filter((label) => label.id === labelId).length === 0
) {
board.filterLabels.add(labelId);
filterText = filterText.substring(0, posSharp);
}
}
board.update({ filterText });
break;
}
default: default:
} }
} }
+67 -7
View File
@@ -1,3 +1,4 @@
import pick from 'lodash/pick';
import { attr, fk, many, oneToOne } from 'redux-orm'; import { attr, fk, many, oneToOne } from 'redux-orm';
import BaseModel from './BaseModel'; import BaseModel from './BaseModel';
@@ -13,6 +14,11 @@ export default class extends BaseModel {
position: attr(), position: attr(),
name: attr(), name: attr(),
description: attr(), description: attr(),
creatorUserId: oneToOne({
to: 'User',
as: 'creatorUser',
relatedName: 'ownCards',
}),
dueDate: attr(), dueDate: attr(),
stopwatch: attr(), stopwatch: attr(),
isSubscribed: attr({ isSubscribed: attr({
@@ -163,9 +169,16 @@ export default class extends BaseModel {
Card.withId(payload.cardLabel.cardId).labels.remove(payload.cardLabel.labelId); Card.withId(payload.cardLabel.cardId).labels.remove(payload.cardLabel.labelId);
} catch {} // eslint-disable-line no-empty } catch {} // eslint-disable-line no-empty
break;
case ActionTypes.LIST_SORT__SUCCESS:
case ActionTypes.LIST_SORT_HANDLE:
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
payload.cards.forEach((card) => {
Card.upsert(card);
});
break; break;
case ActionTypes.CARD_CREATE: case ActionTypes.CARD_CREATE:
case ActionTypes.CARD_CREATE_HANDLE:
case ActionTypes.CARD_UPDATE__SUCCESS: case ActionTypes.CARD_UPDATE__SUCCESS:
case ActionTypes.CARD_UPDATE_HANDLE: case ActionTypes.CARD_UPDATE_HANDLE:
Card.upsert(payload.card); Card.upsert(payload.card);
@@ -176,10 +189,63 @@ export default class extends BaseModel {
Card.upsert(payload.card); Card.upsert(payload.card);
break; break;
case ActionTypes.CARD_CREATE_HANDLE: {
const cardModel = Card.upsert(payload.card);
payload.cardMemberships.forEach(({ userId }) => {
cardModel.users.add(userId);
});
payload.cardLabels.forEach(({ labelId }) => {
cardModel.labels.add(labelId);
});
break;
}
case ActionTypes.CARD_UPDATE: case ActionTypes.CARD_UPDATE:
Card.withId(payload.id).update(payload.data); Card.withId(payload.id).update(payload.data);
break; break;
case ActionTypes.CARD_DUPLICATE: {
const cardModel = Card.withId(payload.id);
const nextCardModel = Card.upsert({
...pick(cardModel.ref, [
'boardId',
'listId',
'position',
'name',
'description',
'dueDate',
'stopwatch',
]),
...payload.card,
});
cardModel.users.toRefArray().forEach(({ id }) => {
nextCardModel.users.add(id);
});
cardModel.labels.toRefArray().forEach(({ id }) => {
nextCardModel.labels.add(id);
});
break;
}
case ActionTypes.CARD_DUPLICATE__SUCCESS: {
Card.withId(payload.localId).deleteWithRelated();
const cardModel = Card.upsert(payload.card);
payload.cardMemberships.forEach(({ userId }) => {
cardModel.users.add(userId);
});
payload.cardLabels.forEach(({ labelId }) => {
cardModel.labels.add(labelId);
});
break;
}
case ActionTypes.CARD_DELETE: case ActionTypes.CARD_DELETE:
Card.withId(payload.id).deleteWithRelated(); Card.withId(payload.id).deleteWithRelated();
@@ -229,12 +295,6 @@ export default class extends BaseModel {
break; break;
} }
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
payload.cards.forEach((card) => {
Card.upsert(card);
});
break;
default: default:
} }
} }
+12
View File
@@ -80,4 +80,16 @@ export default class extends BaseModel {
default: default:
} }
} }
static findLabelsFromText(filterText, labels) {
const selectLabel = filterText.toLocaleLowerCase();
const matchingLabels = labels.filter((label) =>
label.name ? label.name.toLocaleLowerCase().startsWith(selectLabel) : false,
);
if (matchingLabels.length === 1) {
// Appens the user to the filter
return matchingLabels[0].id;
}
return null;
}
} }
+38
View File
@@ -1,6 +1,7 @@
import { attr, fk } from 'redux-orm'; import { attr, fk } from 'redux-orm';
import BaseModel from './BaseModel'; import BaseModel from './BaseModel';
import User from './User';
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
export default class extends BaseModel { export default class extends BaseModel {
@@ -50,6 +51,8 @@ export default class extends BaseModel {
case ActionTypes.LIST_CREATE_HANDLE: case ActionTypes.LIST_CREATE_HANDLE:
case ActionTypes.LIST_UPDATE__SUCCESS: case ActionTypes.LIST_UPDATE__SUCCESS:
case ActionTypes.LIST_UPDATE_HANDLE: case ActionTypes.LIST_UPDATE_HANDLE:
case ActionTypes.LIST_SORT__SUCCESS:
case ActionTypes.LIST_SORT_HANDLE:
List.upsert(payload.list); List.upsert(payload.list);
break; break;
@@ -87,6 +90,41 @@ export default class extends BaseModel {
getFilteredOrderedCardsModelArray() { getFilteredOrderedCardsModelArray() {
let cardModels = this.getOrderedCardsQuerySet().toModelArray(); let cardModels = this.getOrderedCardsQuerySet().toModelArray();
const { filterText } = this.board;
if (filterText !== '') {
let re = null;
const posSpace = filterText.indexOf(' ');
if (filterText.startsWith('/')) {
re = new RegExp(filterText.substring(1), 'i');
}
let doRegularSearch = true;
if (re) {
cardModels = cardModels.filter(
(cardModel) => re.test(cardModel.name) || re.test(cardModel?.description),
);
doRegularSearch = false;
} else if (filterText.startsWith('!') && posSpace > 0) {
const creatorUserId = User.findUsersFromText(
filterText.substring(1, posSpace),
this.board.memberships.toModelArray().map((membership) => membership.user),
);
if (creatorUserId != null) {
doRegularSearch = false;
cardModels = cardModels.filter((cardModel) => cardModel.creatorUser.id === creatorUserId);
}
}
if (doRegularSearch) {
const lowerCasedFilter = filterText.toLocaleLowerCase();
cardModels = cardModels.filter(
(cardModel) =>
cardModel.name.toLocaleLowerCase().indexOf(lowerCasedFilter) >= 0 ||
cardModel.description?.toLocaleLowerCase().indexOf(lowerCasedFilter) >= 0,
);
}
}
const filterUserIds = this.board.filterUsers.toRefArray().map((user) => user.id); const filterUserIds = this.board.filterUsers.toRefArray().map((user) => user.id);
const filterLabelIds = this.board.filterLabels.toRefArray().map((label) => label.id); const filterLabelIds = this.board.filterLabels.toRefArray().map((label) => label.id);
+15
View File
@@ -1,5 +1,6 @@
import { attr, fk } from 'redux-orm'; import { attr, fk } from 'redux-orm';
import { createLocalId } from '../utils/local-id';
import BaseModel from './BaseModel'; import BaseModel from './BaseModel';
import ActionTypes from '../constants/ActionTypes'; import ActionTypes from '../constants/ActionTypes';
@@ -44,10 +45,24 @@ export default class extends BaseModel {
break; break;
case ActionTypes.BOARD_FETCH__SUCCESS: case ActionTypes.BOARD_FETCH__SUCCESS:
case ActionTypes.CARD_CREATE_HANDLE:
case ActionTypes.CARD_DUPLICATE__SUCCESS:
payload.tasks.forEach((task) => { payload.tasks.forEach((task) => {
Task.upsert(task); Task.upsert(task);
}); });
break;
case ActionTypes.CARD_DUPLICATE:
payload.taskIds.forEach((taskId, index) => {
const taskModel = Task.withId(taskId);
Task.upsert({
...taskModel.ref,
id: `${createLocalId()}-${index}`, // TODO: hack?
cardId: payload.card.id,
});
});
break; break;
case ActionTypes.TASK_CREATE: case ActionTypes.TASK_CREATE:
case ActionTypes.TASK_CREATE_HANDLE: case ActionTypes.TASK_CREATE_HANDLE:
+15
View File
@@ -46,6 +46,7 @@ export default class extends BaseModel {
isAdmin: attr(), isAdmin: attr(),
isLocked: attr(), isLocked: attr(),
isRoleLocked: attr(), isRoleLocked: attr(),
isUsernameLocked: attr(),
isDeletionLocked: attr(), isDeletionLocked: attr(),
deletedAt: attr(), deletedAt: attr(),
createdAt: attr({ createdAt: attr({
@@ -358,4 +359,18 @@ export default class extends BaseModel {
}, },
); );
} }
static findUsersFromText(filterText, users) {
const selectUser = filterText.toLocaleLowerCase();
const matchingUsers = users.filter(
(user) =>
user.name.toLocaleLowerCase().startsWith(selectUser) ||
user.username.toLocaleLowerCase().startsWith(selectUser),
);
if (matchingUsers.length === 1) {
// Appens the user to the filter
return matchingUsers[0].id;
}
return null;
}
} }
+2 -4
View File
@@ -1,8 +1,7 @@
import { call, fork, join, put, select, take } from 'redux-saga/effects'; import { call, fork, join, put, select, take } from 'redux-saga/effects';
import selectors from '../../selectors'; import selectors from '../../selectors';
import actions from '../../actions'; import entryActions from '../../entry-actions';
import { removeAccessToken } from '../../utils/access-token-storage';
import ErrorCodes from '../../constants/ErrorCodes'; import ErrorCodes from '../../constants/ErrorCodes';
let lastRequestTask; let lastRequestTask;
@@ -22,8 +21,7 @@ function* queueRequest(method, ...args) {
}); });
} catch (error) { } catch (error) {
if (error.code === ErrorCodes.UNAUTHORIZED) { if (error.code === ErrorCodes.UNAUTHORIZED) {
yield call(removeAccessToken); yield put(entryActions.logout(false));
yield put(actions.logout()); // TODO: next url
yield take(); yield take();
} }
+79 -5
View File
@@ -5,6 +5,7 @@ import request from '../request';
import selectors from '../../../selectors'; import selectors from '../../../selectors';
import actions from '../../../actions'; import actions from '../../../actions';
import api from '../../../api'; import api from '../../../api';
import i18n from '../../../i18n';
import { createLocalId } from '../../../utils/local-id'; import { createLocalId } from '../../../utils/local-id';
export function* createCard(listId, data, autoOpen) { export function* createCard(listId, data, autoOpen) {
@@ -41,8 +42,23 @@ export function* createCard(listId, data, autoOpen) {
} }
} }
export function* handleCardCreate(card) { export function* handleCardCreate({ id }) {
yield put(actions.handleCardCreate(card)); let card;
let cardMemberships;
let cardLabels;
let tasks;
let attachments;
try {
({
item: card,
included: { cardMemberships, cardLabels, tasks, attachments },
} = yield call(request, api.getCard, id));
} catch (error) {
return;
}
yield put(actions.handleCardCreate(card, cardMemberships, cardLabels, tasks, attachments));
} }
export function* updateCard(id, data) { export function* updateCard(id, data) {
@@ -70,7 +86,7 @@ export function* handleCardUpdate(card) {
yield put(actions.handleCardUpdate(card)); yield put(actions.handleCardUpdate(card));
} }
export function* moveCard(id, listId, index) { export function* moveCard(id, listId, index = 0) {
const position = yield select(selectors.selectNextCardPosition, listId, index, id); const position = yield select(selectors.selectNextCardPosition, listId, index, id);
yield call(updateCard, id, { yield call(updateCard, id, {
@@ -85,7 +101,7 @@ export function* moveCurrentCard(listId, index) {
yield call(moveCard, cardId, listId, index); yield call(moveCard, cardId, listId, index);
} }
export function* transferCard(id, boardId, listId, index) { export function* transferCard(id, boardId, listId, index = 0) {
const { cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath); const { cardId: currentCardId, boardId: currentBoardId } = yield select(selectors.selectPath);
const position = yield select(selectors.selectNextCardPosition, listId, index, id); const position = yield select(selectors.selectNextCardPosition, listId, index, id);
@@ -106,6 +122,55 @@ export function* transferCurrentCard(boardId, listId, index) {
yield call(transferCard, cardId, boardId, listId, index); yield call(transferCard, cardId, boardId, listId, index);
} }
export function* duplicateCard(id) {
const { listId, name } = yield select(selectors.selectCardById, id);
const index = yield select(selectors.selectCardIndexById, id);
const nextData = {
position: yield select(selectors.selectNextCardPosition, listId, index + 1),
name: `${name} (${i18n.t('common.copy', {
context: 'inline',
})})`,
};
const localId = yield call(createLocalId);
const taskIds = yield select(selectors.selectTaskIdsByCardId, id);
yield put(
actions.duplicateCard(
id,
{
...nextData,
id: localId,
},
taskIds,
),
);
let card;
let cardMemberships;
let cardLabels;
let tasks;
try {
({
item: card,
included: { cardMemberships, cardLabels, tasks },
} = yield call(request, api.duplicateCard, id, nextData));
} catch (error) {
yield put(actions.duplicateCard.failure(localId, error));
return;
}
yield put(actions.duplicateCard.success(localId, card, cardMemberships, cardLabels, tasks));
}
export function* duplicateCurrentCard() {
const { cardId } = yield select(selectors.selectPath);
yield call(duplicateCard, cardId);
}
export function* deleteCard(id) { export function* deleteCard(id) {
const { cardId, boardId } = yield select(selectors.selectPath); const { cardId, boardId } = yield select(selectors.selectPath);
@@ -142,17 +207,26 @@ export function* handleCardDelete(card) {
yield put(actions.handleCardDelete(card)); yield put(actions.handleCardDelete(card));
} }
export function* handleTextFilter(text) {
const { boardId } = yield select(selectors.selectPath);
yield put(actions.filterText(boardId, text));
}
export default { export default {
createCard, createCard,
handleCardCreate, handleCardCreate,
updateCard, updateCard,
updateCurrentCard, updateCurrentCard,
handleCardUpdate,
moveCard, moveCard,
moveCurrentCard, moveCurrentCard,
transferCard, transferCard,
transferCurrentCard, transferCurrentCard,
handleCardUpdate, duplicateCard,
duplicateCurrentCard,
deleteCard, deleteCard,
deleteCurrentCard, deleteCurrentCard,
handleCardDelete, handleCardDelete,
handleTextFilter,
}; };
+2 -3
View File
@@ -1,4 +1,4 @@
import { call, put, select, take } from 'redux-saga/effects'; import { call, put, select } from 'redux-saga/effects';
import request from '../request'; import request from '../request';
import requests from '../requests'; import requests from '../requests';
@@ -84,8 +84,7 @@ export function* logout(invalidateAccessToken = true) {
} catch (error) {} // eslint-disable-line no-empty } catch (error) {} // eslint-disable-line no-empty
} }
yield put(actions.logout()); yield put(actions.logout()); // TODO: next url
yield take();
} }
export default { export default {
+26
View File
@@ -70,6 +70,30 @@ export function* moveList(id, index) {
}); });
} }
// TODO: sort locally
export function* sortList(id, data) {
yield put(actions.sortList(id, data));
let list;
let cards;
try {
({
item: list,
included: { cards },
} = yield call(request, api.sortList, id, data));
} catch (error) {
yield put(actions.sortList.failure(id, error));
return;
}
yield put(actions.sortList.success(list, cards));
}
export function* handleListSort(list, cards) {
yield put(actions.handleListSort(list, cards));
}
export function* deleteList(id) { export function* deleteList(id) {
yield put(actions.deleteList(id)); yield put(actions.deleteList(id));
@@ -95,6 +119,8 @@ export default {
updateList, updateList,
handleListUpdate, handleListUpdate,
moveList, moveList,
sortList,
handleListSort,
deleteList, deleteList,
handleListDelete, handleListDelete,
}; };
+9
View File
@@ -1,10 +1,12 @@
import { call, put, select, take } from 'redux-saga/effects'; import { call, put, select, take } from 'redux-saga/effects';
import { push } from '../../../lib/redux-router'; import { push } from '../../../lib/redux-router';
import { logout } from './core';
import request from '../request'; import request from '../request';
import selectors from '../../../selectors'; import selectors from '../../../selectors';
import actions from '../../../actions'; import actions from '../../../actions';
import api from '../../../api'; import api from '../../../api';
import { getAccessToken } from '../../../utils/access-token-storage';
import ActionTypes from '../../../constants/ActionTypes'; import ActionTypes from '../../../constants/ActionTypes';
import Paths from '../../../constants/Paths'; import Paths from '../../../constants/Paths';
@@ -25,6 +27,13 @@ export function* goToCard(cardId) {
} }
export function* handleLocationChange() { export function* handleLocationChange() {
const accessToken = yield call(getAccessToken);
if (!accessToken) {
yield call(logout, false);
return;
}
const pathsMatch = yield select(selectors.selectPathsMatch); const pathsMatch = yield select(selectors.selectPathsMatch);
if (!pathsMatch) { if (!pathsMatch) {
+1
View File
@@ -218,6 +218,7 @@ export function* handleUserDelete(user) {
if (user.id === currentUserId) { if (user.id === currentUserId) {
yield call(logout, false); yield call(logout, false);
return;
} }
yield put(actions.handleUserDelete(user)); yield put(actions.handleUserDelete(user));

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