Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1fd694248 | ||
|
|
634d6ceab1 | ||
|
|
31d4d5f38d | ||
|
|
3f99438402 | ||
|
|
7e7727e3e1 | ||
|
|
616304a1fa | ||
|
|
1e01148138 | ||
|
|
ede56e1ae3 | ||
|
|
b2e0c45539 | ||
|
|
389648b546 | ||
|
|
f4789c28db | ||
|
|
668092de7d | ||
|
|
920f9c097d | ||
|
|
da63598de3 | ||
|
|
517cb34cfe | ||
|
|
079b8eba53 | ||
|
|
af02cdd8bf | ||
|
|
e1bc189b88 | ||
|
|
5f2723623a | ||
|
|
fee6841d0a | ||
|
|
ca6fb3b962 | ||
|
|
28c3f28e01 | ||
|
|
80d12aaeeb | ||
|
|
89af209a2e | ||
|
|
f1fed3e533 | ||
|
|
eddeb96b16 | ||
|
|
9e8ff3b579 | ||
|
|
fc1278011c | ||
|
|
24163463fc | ||
|
|
fdef87e3b9 | ||
|
|
964498dbaa | ||
|
|
5b519cb62f | ||
|
|
11fc3b4485 | ||
|
|
37c0b59f82 | ||
|
|
239611ad51 | ||
|
|
0575e665fe | ||
|
|
019766dced | ||
|
|
6dc9e4ed99 | ||
|
|
d4b64b90fc | ||
|
|
1a49826b85 | ||
|
|
815643eb2c | ||
|
|
bcc4c0d923 | ||
|
|
cf7810b067 | ||
|
|
f1c72df8b6 | ||
|
|
5056d38848 | ||
|
|
3ef5b3ead8 | ||
|
|
ded58dc8b0 | ||
|
|
d8ee1ecdd1 | ||
|
|
fc2f4cd5d3 | ||
|
|
7ef457157e | ||
|
|
23ee815200 | ||
|
|
40c04c35ff | ||
|
|
743f2956c8 | ||
|
|
5bf5db27d8 | ||
|
|
667c5daf25 | ||
|
|
6662b0a717 | ||
|
|
348bf4d6c4 | ||
|
|
3ac2d289d0 | ||
|
|
3bc73c43ea | ||
|
|
8e0c60f5be | ||
|
|
aae69cb5e4 | ||
|
|
782c0356c8 | ||
|
|
757cdb14a4 | ||
|
|
8a4bdbd1fe | ||
|
|
5bf42be786 | ||
|
|
d896c3183b | ||
|
|
13ee0c3466 | ||
|
|
211c841379 | ||
|
|
90a30961ff | ||
|
|
7cb9774e05 | ||
|
|
875895b331 | ||
|
|
91bc889fed | ||
|
|
a34d8e9753 | ||
|
|
107cb85ba2 | ||
|
|
9333ef562e | ||
|
|
7d28abc7e6 | ||
|
|
5317950b5c | ||
|
|
7501619b7e | ||
|
|
080e29aa03 | ||
|
|
48c532b2c2 | ||
|
|
a62815f825 | ||
|
|
6938232dc4 | ||
|
|
4c4cfa9c66 | ||
|
|
ba89d41b19 | ||
|
|
5c435414f7 | ||
|
|
2d7812a679 | ||
|
|
d386c82973 | ||
|
|
6c4db967c2 | ||
|
|
932d8225ed | ||
|
|
0d481703da | ||
|
|
23e2b4a622 | ||
|
|
f4cc51e1c0 | ||
|
|
793ddd5e20 | ||
|
|
3d84888eb1 | ||
|
|
02eabd203c | ||
|
|
d0a2734161 | ||
|
|
eea57ff121 | ||
|
|
7e2b74627c | ||
|
|
bee199c13b | ||
|
|
8dd331b895 | ||
|
|
13f26b4be1 |
@@ -0,0 +1,57 @@
|
|||||||
|
name: Build and publish release package
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-publish-release-package:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Workflow install pnpm
|
||||||
|
run: npm install pnpm -g
|
||||||
|
|
||||||
|
- name: Client install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Server install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Server include into dist
|
||||||
|
run: mv server/ dist/
|
||||||
|
|
||||||
|
- name: Client build production
|
||||||
|
run: |
|
||||||
|
npm run build
|
||||||
|
working-directory: ./client
|
||||||
|
|
||||||
|
- name: Client include into dist
|
||||||
|
run: |
|
||||||
|
mv build/index.html ../dist/views/index.ejs
|
||||||
|
mv build/* ../dist/public/
|
||||||
|
working-directory: ./client
|
||||||
|
|
||||||
|
- name: Dist include README.md SECURITY.md LICENSE start.sh
|
||||||
|
run: mv README.md SECURITY.md LICENSE start.sh dist/
|
||||||
|
|
||||||
|
- name: Dist Remove node modules
|
||||||
|
run: rm -R dist/node_modules
|
||||||
|
|
||||||
|
- name: Dist create .zip file
|
||||||
|
run: |
|
||||||
|
mv dist/ planka/
|
||||||
|
zip -r planka-prebuild-${{ github.event.release.tag_name }}.zip planka
|
||||||
|
|
||||||
|
- name: Dist upload assets
|
||||||
|
run: |
|
||||||
|
gh release upload ${{ github.event.release.tag_name }} planka-prebuild-${{ github.event.release.tag_name }}.zip
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
@@ -13,26 +13,26 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push-docker-base-image:
|
build-and-push-docker-base-image:
|
||||||
runs-on: ubuntu-latest
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v1
|
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
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v2
|
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
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||||
|
name: Build and push Docker DEV image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/**'
|
||||||
|
- 'charts/**'
|
||||||
|
- 'docker-*.sh'
|
||||||
|
- '*.md'
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY_IMAGE: ghcr.io/plankanban/planka
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
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:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY_IMAGE }}
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||||
|
- name: Export digest
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/digests
|
||||||
|
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
|
||||||
@@ -6,19 +6,19 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push-docker-image:
|
build-and-push-docker-image:
|
||||||
runs-on: ubuntu-latest
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
script: return context.payload.release.tag_name.replace('v', '')
|
script: return context.payload.release.tag_name.replace('v', '')
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v2
|
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
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: Release Charts
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "charts/**"
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
# depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
|
||||||
|
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config user.name "$GITHUB_ACTOR"
|
||||||
|
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
|
||||||
|
- name: Add repositories
|
||||||
|
run: |
|
||||||
|
for dir in $(ls -d charts/*/); do
|
||||||
|
helm dependency list $dir 2> /dev/null | tail +2 | head -n -1 | awk '{ print "helm repo add " $1 " " $3 }' | while read cmd; do $cmd; done
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Run chart-releaser for stable
|
||||||
|
uses: helm/chart-releaser-action@v1.6.0
|
||||||
|
with:
|
||||||
|
charts_dir: charts
|
||||||
|
mark_as_latest: false
|
||||||
|
env:
|
||||||
|
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
@@ -10,3 +10,6 @@ docker-compose.override.yml
|
|||||||
# `npm i --package-lock-only`
|
# `npm i --package-lock-only`
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|
||||||
|
# Chart dependencies
|
||||||
|
**/charts/*.tgz
|
||||||
|
|||||||
+15
-5
@@ -1,11 +1,17 @@
|
|||||||
FROM ghcr.io/plankanban/planka:base-latest as server-dependencies
|
FROM node:18-alpine as server-dependencies
|
||||||
|
|
||||||
|
RUN apk -U upgrade \
|
||||||
|
&& apk add build-base python3 \
|
||||||
|
--no-cache
|
||||||
|
|
||||||
WORKDIR /app
|
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 clean-install --omit=dev
|
&& npm install pnpm --global \
|
||||||
|
&& pnpm import \
|
||||||
|
&& pnpm install --prod
|
||||||
|
|
||||||
FROM node:lts AS client
|
FROM node:lts AS client
|
||||||
|
|
||||||
@@ -14,14 +20,18 @@ 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 clean-install --omit=dev
|
&& npm install pnpm --global \
|
||||||
|
&& pnpm import \
|
||||||
|
&& pnpm install --prod
|
||||||
|
|
||||||
COPY client .
|
COPY client .
|
||||||
RUN DISABLE_ESLINT_PLUGIN=true npm run build
|
RUN DISABLE_ESLINT_PLUGIN=true npm run build
|
||||||
|
|
||||||
FROM ghcr.io/plankanban/planka:base-latest
|
FROM node:18-alpine
|
||||||
|
|
||||||
RUN apk del vips-dependencies --purge
|
RUN apk -U upgrade \
|
||||||
|
&& apk add bash \
|
||||||
|
--no-cache
|
||||||
|
|
||||||
USER node
|
USER node
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
+12
-17
@@ -1,27 +1,22 @@
|
|||||||
FROM node:lts-alpine
|
FROM node:18-alpine
|
||||||
|
|
||||||
ARG ALPINE_VERSION=3.16
|
ARG VIPS_VERSION=8.14.5
|
||||||
ARG VIPS_VERSION=8.13.3
|
|
||||||
|
|
||||||
RUN apk -U upgrade \
|
RUN apk -U upgrade \
|
||||||
&& apk add \
|
&& apk add \
|
||||||
bash giflib glib lcms2 libexif \
|
bash pkgconf \
|
||||||
libgsf libjpeg-turbo libpng librsvg libwebp \
|
libjpeg-turbo libexif librsvg cgif tiff libspng libimagequant \
|
||||||
orc pango tiff \
|
|
||||||
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/community/ \
|
|
||||||
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/main/ \
|
|
||||||
--no-cache \
|
--no-cache \
|
||||||
&& apk add \
|
&& apk add \
|
||||||
build-base giflib-dev glib-dev lcms2-dev libexif-dev \
|
build-base gobject-introspection-dev meson \
|
||||||
libgsf-dev libjpeg-turbo-dev libpng-dev librsvg-dev libwebp-dev \
|
libjpeg-turbo-dev libexif-dev librsvg-dev cgif-dev tiff-dev libspng-dev libimagequant-dev \
|
||||||
orc-dev pango-dev tiff-dev \
|
|
||||||
--virtual vips-dependencies \
|
--virtual vips-dependencies \
|
||||||
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/community/ \
|
|
||||||
--repository https://alpine.global.ssl.fastly.net/alpine/v${ALPINE_VERSION}/main/ \
|
|
||||||
--no-cache \
|
--no-cache \
|
||||||
&& wget -O- https://github.com/libvips/libvips/releases/download/v${VIPS_VERSION}/vips-${VIPS_VERSION}.tar.gz | tar xzC /tmp \
|
&& wget -O- https://github.com/libvips/libvips/releases/download/v${VIPS_VERSION}/vips-${VIPS_VERSION}.tar.xz | tar xJC /tmp \
|
||||||
&& cd /tmp/vips-${VIPS_VERSION} \
|
&& cd /tmp/vips-${VIPS_VERSION} \
|
||||||
&& ./configure \
|
&& meson setup build-dir \
|
||||||
&& make \
|
&& cd build-dir \
|
||||||
&& make install-strip \
|
&& ninja \
|
||||||
|
&& ninja test \
|
||||||
|
&& ninja install \
|
||||||
&& rm -rf /tmp/vips-${VIPS_VERSION}
|
&& rm -rf /tmp/vips-${VIPS_VERSION}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# Planka
|
# Planka
|
||||||
|
#### Elegant open source project tracking.
|
||||||
|
|
||||||
  
|
  
|
||||||
|
|
||||||
A Trello-like kanban board built with React and Redux.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[**Client demo**](https://plankanban.github.io/planka) (without server features).
|
[**Client demo**](https://plankanban.github.io/planka) (without server features).
|
||||||
@@ -19,306 +18,22 @@ A Trello-like kanban board built with React and Redux.
|
|||||||
- User notifications
|
- User notifications
|
||||||
- Internationalization
|
- Internationalization
|
||||||
|
|
||||||
## Deploy
|
## How to deploy Planka
|
||||||
|
|
||||||
There are 2 types of installation:
|
There are many ways to install Planka, [check them out](https://docs.planka.cloud/docs/intro).
|
||||||
1. [Dockerized](#1-docker-compose)
|
|
||||||
2. [Without Docker](#2-without-docker)
|
|
||||||
|
|
||||||
### 1. Docker Compose
|
For configuration, please see the [configuration section](https://docs.planka.cloud/docs/category/configuration).
|
||||||
|
|
||||||
[](https://github.com/plankanban/planka/pkgs/container/planka)
|
## Contact
|
||||||
|
|
||||||
- Make sure you have [Docker](https://docs.docker.com/install/) and [Docker Compose](https://docs.docker.com/compose/install/) installed and operational.
|
- If you want to get a hosted version of Planka, you can contact us via email contact@planka.cloud
|
||||||
- Create `docker-compose.yml` based on [the example](https://raw.githubusercontent.com/plankanban/planka/master/docker-compose.yml). This is the ONLY file you will need. You can create this file on your own machine by copy and pasting the content.
|
- For any security issues, please do not create a public issue on GitHub, instead please write to security@planka.cloud
|
||||||
- Edit `BASE_URL` to match your domain name or IP address.
|
|
||||||
- Edit `SECRET_KEY` with random value. You can generate it by `openssl rand -hex 64`.
|
|
||||||
|
|
||||||
Download the docker-compose.yml:
|
We do NOT offer any public support via email, please use GitHub.
|
||||||
|
|
||||||
```
|
|
||||||
curl -L https://raw.githubusercontent.com/plankanban/planka/master/docker-compose.yml -o docker-compose.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
Pull images and start services:
|
|
||||||
|
|
||||||
```
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Demo user: demo@demo.demo demo
|
|
||||||
|
|
||||||
### 2. Without Docker
|
|
||||||
|
|
||||||
Installing without Docker is a bit more complicated, here's what you need to do:
|
|
||||||
|
|
||||||
1. Clone this repository into a directory of your choice. (e.g. `/var/www/planka`)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p /var/www/planka
|
|
||||||
cd /var/www/planka
|
|
||||||
git clone https://github.com/plankanban/planka.git .
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install dependencies and build the client.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
npm run client:build
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: You can use `yarn` or `pnpm` instead of `npm`.
|
|
||||||
|
|
||||||
3. Copy the content of the `client/build` directory to the server.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp -r client/build/. server/public
|
|
||||||
cp client/build/index.html server/views/index.ejs
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Copy the start script and navigate to the `server` directory.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp start.sh server
|
|
||||||
cd server
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Configure environment variables.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp .env.sample .env
|
|
||||||
|
|
||||||
# Edit .env file (You could use nano, vim, etc.)
|
|
||||||
nano .env
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: Before continuing, make sure you have your selected database created and running.
|
|
||||||
|
|
||||||
6. Start the server.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./start.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: You can use `pm2` or `systemd` to run the server in the background.
|
|
||||||
|
|
||||||
## Additional information
|
|
||||||
|
|
||||||
### Nginx configuration
|
|
||||||
|
|
||||||
Here is an example of Nginx configuration for Planka, make sure to replace `<domain>` with your domain name, and make sure to configure the SSL.
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
upstream planka {
|
|
||||||
server localhost:1337;
|
|
||||||
keepalive 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
listen [::]:443 ssl http2;
|
|
||||||
server_name <domain>;
|
|
||||||
|
|
||||||
access_log /var/log/nginx/planka-access.log;
|
|
||||||
error_log /var/log/nginx/planka-error.log error;
|
|
||||||
|
|
||||||
# SSL Configuration - Replace the example <domain> with your domain
|
|
||||||
ssl_certificate /etc/letsencrypt/live/<domain>/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/<domain>/privkey.pem;
|
|
||||||
ssl_session_cache shared:SSL:10m;
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
|
|
||||||
client_max_body_size 120M;
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
add_header Access-Control-Max-Age 3600;
|
|
||||||
add_header Access-Control-Expose-Headers Content-Length;
|
|
||||||
add_header Access-Control-Allow-Headers Range;
|
|
||||||
|
|
||||||
# Make sure to allow socket.io connections
|
|
||||||
location ~* \.io {
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
client_max_body_size 50M;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Frame-Options SAMEORIGIN;
|
|
||||||
proxy_buffers 256 16k;
|
|
||||||
proxy_buffer_size 16k;
|
|
||||||
client_body_timeout 60;
|
|
||||||
send_timeout 300;
|
|
||||||
lingering_timeout 5;
|
|
||||||
proxy_connect_timeout 1d;
|
|
||||||
proxy_send_timeout 1d;
|
|
||||||
proxy_read_timeout 1d;
|
|
||||||
proxy_pass http://planka;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
client_max_body_size 50M;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Frame-Options SAMEORIGIN;
|
|
||||||
proxy_buffers 256 16k;
|
|
||||||
proxy_buffer_size 16k;
|
|
||||||
proxy_read_timeout 600s;
|
|
||||||
proxy_cache_revalidate on;
|
|
||||||
proxy_cache_min_uses 2;
|
|
||||||
proxy_cache_use_stale timeout;
|
|
||||||
proxy_cache_lock on;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_pass http://planka;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Logging
|
|
||||||
|
|
||||||
Planka currently allows you to expose the application's logfile directory to the host machine via a shared volume. This feature is not enabled by default.
|
|
||||||
|
|
||||||
To expose the logfile director to the host machine, add the item `./logs/:/app/logs/` under `services.planka.volumes`.
|
|
||||||
|
|
||||||
Note that the directory to the left of the semicolon is regarding the host machine while the directory to the right of the semicolon is regarding the Docker container.
|
|
||||||
|
|
||||||
For example, in the above step, `./logs/:/app/logs/` will create the folder `logs` in the same directory where the `docker-compose.yml` file lives.
|
|
||||||
|
|
||||||
### Rotating Logs
|
|
||||||
|
|
||||||
Logrotate is designed to ease administration of systems that generate large numbers of log files. It allows automatic rotation, compression, removal, and mailing of log files. Each log file may be handled daily, weekly, monthly, or when it grows too large.
|
|
||||||
|
|
||||||
#### Setup logrotate for Planka logs
|
|
||||||
|
|
||||||
Create a file in `/etc/logrotate.d` named `planka` with the following contents:
|
|
||||||
|
|
||||||
```
|
|
||||||
/path/to/planka/logs/planka.log {
|
|
||||||
daily
|
|
||||||
missingok
|
|
||||||
rotate 14
|
|
||||||
compress
|
|
||||||
delaycompress
|
|
||||||
notifempty
|
|
||||||
create 640 root adm
|
|
||||||
sharedscripts
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Ensure to replace logfile directory with your installation’s `/logs/planka.log` location.
|
|
||||||
|
|
||||||
Restart the logrotate service.
|
|
||||||
|
|
||||||
### Fail2ban
|
|
||||||
|
|
||||||
Fail2ban is a service that uses iptables to automatically drop connections for a pre-defined amount of time from IPs that continuously failed to authenticate to the configured services.
|
|
||||||
|
|
||||||
#### Setup a filter and a jail for Planka
|
|
||||||
|
|
||||||
A filter defines regex rules to identify when users fail to authenticate on Planka's user interface.
|
|
||||||
|
|
||||||
Create a file in `/etc/fail2ban/filter.d` named `planka.conf` with the following contents:
|
|
||||||
|
|
||||||
```conf
|
|
||||||
[Definition]
|
|
||||||
failregex = ^(.*) Invalid (email or username:|password!) (\"(.*)\"!)? ?\(IP: <ADDR>\)$
|
|
||||||
ignoreregex =
|
|
||||||
```
|
|
||||||
|
|
||||||
The jail file defines how to handle the failed authentication attempts found by the Planka filter.
|
|
||||||
|
|
||||||
Create a file in `/etc/fail2ban/jail.d` named `planka.local` with the following contents:
|
|
||||||
|
|
||||||
```conf
|
|
||||||
[planka]
|
|
||||||
enabled = true
|
|
||||||
port = http,https
|
|
||||||
filter = planka
|
|
||||||
logpath = /path/to/planka/logs/planka.log
|
|
||||||
maxretry = 5
|
|
||||||
bantime = 900
|
|
||||||
```
|
|
||||||
|
|
||||||
Ensure to replace `logpath`'s value with your installation’s `/logs/planka.log` location. If you are using ports other than 80 and 443 for your Web server you should replace those too. The bantime and findtime are defined in seconds.
|
|
||||||
|
|
||||||
Restart the fail2ban service. You can check the status of your Planka jail by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
fail2ban-client status planka
|
|
||||||
```
|
|
||||||
|
|
||||||
### Import from Trello
|
|
||||||
|
|
||||||
It's already available in Planka, or you can also use the great tool [trello2planka](https://github.com/christophenne/trello2planka) to do the import.
|
|
||||||
|
|
||||||
### Backup and Restore
|
|
||||||
|
|
||||||
Planka comes with two scripts that allow for manual backup and restore when running Planka with docker-compose.yml.
|
|
||||||
Backups can be triggered with `docker-backup.sh` which will export the Database, User Avatars, Project Backgrounds and Attachments into a single tgz file.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./docker-backup.sh
|
|
||||||
Exporting postgres database ... Success!
|
|
||||||
Exporting user-avatars ... Success!
|
|
||||||
Exporting project-background-images ... Success!
|
|
||||||
Exporting attachments ... Success!
|
|
||||||
Creating final tarball 2023-01-17T15-37-22Z-backup.tgz ... Success!
|
|
||||||
Cleaning up temporary files and folders ... Success!
|
|
||||||
Backup Complete!
|
|
||||||
```
|
|
||||||
|
|
||||||
The resulting backup can be restored using the `docker-restore.sh` script.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./docker-restore.sh 2023-01-17T15-37-22Z-backup.tgz
|
|
||||||
Extracting tarball 2023-01-17T11-10-54Z-backup.tgz ... Success!
|
|
||||||
Importing postgres database ...
|
|
||||||
|
|
||||||
[Many lines of postgres output]
|
|
||||||
...
|
|
||||||
|
|
||||||
Success!
|
|
||||||
Importing user-avatars ... Success!
|
|
||||||
Importing project-background-images ... Success!
|
|
||||||
Importing attachments ... Success!
|
|
||||||
Cleaning up temporary files and folders ... Success!
|
|
||||||
Restore complete!
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Clone the repository and install dependencies:
|
See the [development section](https://docs.planka.cloud/docs/Development).
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://github.com/plankanban/planka.git
|
|
||||||
|
|
||||||
cd planka
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Either use a local database or start the provided development database:
|
|
||||||
|
|
||||||
```
|
|
||||||
docker-compose -f docker-compose-dev.yml up
|
|
||||||
```
|
|
||||||
|
|
||||||
Create `server/.env` based on `server/.env.sample` and edit `DATABASE_URL` if needed, then initialize the database:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run server:db:init
|
|
||||||
```
|
|
||||||
|
|
||||||
Start the development server:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
Demo user: demo@demo.demo demo
|
|
||||||
|
|
||||||
## Tech stack
|
## Tech stack
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ Most recent release.
|
|||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Please report any security issues you discovered to meltyshev@gmail.com. If the issue is confirmed, we will release a patch as soon as possible depending on complexity.
|
Please report any security issues you discovered to security@planka.cloud. If the issue is confirmed, we will release a patch as soon as possible depending on complexity.
|
||||||
|
|
||||||
**Do NOT create public issues on GitHub for security vulnerabilities.**
|
**Do NOT create public issues on GitHub for security vulnerabilities.**
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.orig
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
dependencies:
|
||||||
|
- name: postgresql
|
||||||
|
repository: https://charts.bitnami.com/bitnami
|
||||||
|
version: 12.5.1
|
||||||
|
digest: sha256:01dfb2d07ab6800b4a5a6c81f20f3377a758124b2b96b891d0cd6b4f64cf783b
|
||||||
|
generated: "2023-05-15T00:54:48.1308917+01:00"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: planka
|
||||||
|
description: A Helm chart to deploy Planka and it's dependencies.
|
||||||
|
|
||||||
|
# A chart can be either an 'application' or a 'library' chart.
|
||||||
|
#
|
||||||
|
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||||
|
# to be deployed.
|
||||||
|
#
|
||||||
|
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||||
|
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||||
|
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||||
|
type: application
|
||||||
|
|
||||||
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
|
# to the chart and its templates, including the app version.
|
||||||
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
|
version: 0.1.16
|
||||||
|
|
||||||
|
# This is the version number of the application being deployed. This version number should be
|
||||||
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
|
# It is recommended to use it with quotes.
|
||||||
|
appVersion: "1.15.5"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- alias: postgresql
|
||||||
|
condition: postgresql.enabled
|
||||||
|
name: postgresql
|
||||||
|
repository: &bitnami-repo https://charts.bitnami.com/bitnami
|
||||||
|
version: 12.5.1
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Planka Helm Chart
|
||||||
|
|
||||||
|
[Planka](https://github.com/plankanban/planka) is an OSS alternative to Trello that you can host yourself, and this is a Helm Chart to make it easier to deploy to K8s.
|
||||||
|
|
||||||
|
Shoutout to [this issue](https://github.com/plankanban/planka/issues/192) who have been asking for this Helm Chart.
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
By using the Bitnami chart for PostgreSQL, there is an issue where once deployed, if trying to use a different password then it will be ignored as the Persistant Volume (PV) will already exist with the previous password. See warning from Bitnami below:
|
||||||
|
|
||||||
|
> **Warning!** Setting a password will be ignored on new installation in the case when previous Posgresql release was deleted through the helm command. In that case, old PVC will have an old password, and setting it through helm won't take effect. Deleting persistent volumes (PVs) will solve the issue. Refer to [issue 2061](https://github.com/bitnami/charts/issues/2061) for more details
|
||||||
|
|
||||||
|
If you want to fully uninstall this chart including the data, follow [these steps](https://github.com/bitnami/charts/blob/main/bitnami/postgresql/README.md#uninstalling-the-chart) from the Bitnami Chart's docs.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The basic usage of the chart can be found below:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Chris-Greaves/planka-helm-chart.git
|
||||||
|
cd planka-helm-chart
|
||||||
|
helm dependency build
|
||||||
|
export SECRETKEY=$(openssl rand -hex 64)
|
||||||
|
helm install planka . --set secretkey=$SECRETKEY \
|
||||||
|
--set admin_email="demo@demo.demo" \
|
||||||
|
--set admin_password="demo" \
|
||||||
|
--set admin_name="Demo Demo" \
|
||||||
|
--set admin_username="demo"
|
||||||
|
```
|
||||||
|
|
||||||
|
> **NOTE:** The command `openssl rand -hex 64` is needed to create a random hexadecimal key for planka. On Windows you can use Git Bash to run that command.
|
||||||
|
|
||||||
|
To access Planka you can port forward using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl port-forward $POD_NAME 3000:1337
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing Externally
|
||||||
|
|
||||||
|
To access Planka externally you can use the following configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# HTTP only
|
||||||
|
helm install planka . --set secretkey=$SECRETKEY \
|
||||||
|
--set admin_email="demo@demo.demo" \
|
||||||
|
--set admin_password="demo" \
|
||||||
|
--set admin_name="Demo Demo" \
|
||||||
|
--set admin_username="demo"
|
||||||
|
--set ingress.enabled=true \
|
||||||
|
--set ingress.hosts[0].host=planka.example.dev \
|
||||||
|
|
||||||
|
# HTTPS
|
||||||
|
helm install planka . --set secretkey=$SECRETKEY \
|
||||||
|
--set admin_email="demo@demo.demo" \
|
||||||
|
--set admin_password="demo" \
|
||||||
|
--set admin_name="Demo Demo" \
|
||||||
|
--set admin_username="demo"
|
||||||
|
--set ingress.enabled=true \
|
||||||
|
--set ingress.hosts[0].host=planka.example.dev \
|
||||||
|
--set ingress.tls[0].secretName=planka-tls \
|
||||||
|
--set ingress.tls[0].hosts[0]=planka.example.dev \
|
||||||
|
```
|
||||||
|
|
||||||
|
or create a values.yaml file like:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
secretkey: "<InsertSecretKey>"
|
||||||
|
# The admin section needs to be present for new instances of Planka, after the first start you can remove the lines starting with admin_. If you want the admin user to be unchangeable admin_email: has to stay
|
||||||
|
# After changing the config you have to run ```helm upgrade planka . -f values.yaml```
|
||||||
|
|
||||||
|
# Admin user
|
||||||
|
admin_email: "demo@demo.demo" # Do not remove if you want to prevent this user from being edited/deleted
|
||||||
|
admin_password: "demo"
|
||||||
|
admin_name: "Demo Demo"
|
||||||
|
admin_username: "demo"
|
||||||
|
# Admin user
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
hosts:
|
||||||
|
- host: planka.example.dev
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
|
||||||
|
# Needed for HTTPS
|
||||||
|
tls:
|
||||||
|
- secretName: planka-tls # existing TLS secret in k8s
|
||||||
|
hosts:
|
||||||
|
- planka.example.dev
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install planka . -f values.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Things to consider if production hosting
|
||||||
|
|
||||||
|
If you want to host Planka for more than just playing around with, you might want to do the following things:
|
||||||
|
|
||||||
|
- Create a `values.yaml` with your config, as this will make applying upgrades much easier in the future.
|
||||||
|
- Create your `secretkey` once and store it either in a secure vault, or in your `values.yaml` file so it will be the same for upgrading in the future.
|
||||||
|
- Specify a password for `postgresql.auth.password` as there have been issues with the postgresql chart generating new passwords locking you out of the data you've already stored. (see [this issue](https://github.com/bitnami/charts/issues/2061))
|
||||||
|
|
||||||
|
Any questions or concerns, [raise an issue](https://github.com/Chris-Greaves/planka-helm-chart/issues/new).
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
1. Get the application URL by running these commands:
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
{{- range $host := .Values.ingress.hosts }}
|
||||||
|
{{- range .paths }}
|
||||||
|
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if contains "NodePort" .Values.service.type }}
|
||||||
|
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "planka.fullname" . }})
|
||||||
|
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||||
|
echo http://$NODE_IP:$NODE_PORT
|
||||||
|
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||||
|
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||||
|
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "planka.fullname" . }}'
|
||||||
|
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "planka.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||||
|
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||||
|
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||||
|
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "planka.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||||
|
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||||
|
echo "Visit http://localhost:3000 to use your application"
|
||||||
|
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 3000:$CONTAINER_PORT
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "planka.chart" . }}
|
||||||
|
{{ include "planka.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "planka.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "planka.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "planka.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "planka.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
{{- if not .Values.autoscaling.enabled }}
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
{{- end }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "planka.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
{{- with .Values.podAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
serviceAccountName: {{ include "planka.serviceAccountName" . }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: {{ .Values.service.containerPort | default 1337 }}
|
||||||
|
protocol: TCP
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /app/public/user-avatars
|
||||||
|
subPath: user-avatars
|
||||||
|
name: planka
|
||||||
|
- mountPath: /app/public/project-background-images
|
||||||
|
subPath: project-background-images
|
||||||
|
name: planka
|
||||||
|
- mountPath: /app/private/attachments
|
||||||
|
subPath: attachments
|
||||||
|
name: planka
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
env:
|
||||||
|
- name: DATABASE_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: planka-postgresql-svcbind-custom-user
|
||||||
|
key: uri
|
||||||
|
- name: BASE_URL
|
||||||
|
{{- if .Values.baseUrl }}
|
||||||
|
value: {{ .Values.baseUrl }}
|
||||||
|
{{- else if .Values.ingress.enabled }}
|
||||||
|
value: {{ printf "https://%s" (first .Values.ingress.hosts).host }}
|
||||||
|
{{- else }}
|
||||||
|
value: http://localhost:3000
|
||||||
|
{{- end }}
|
||||||
|
- name: SECRET_KEY
|
||||||
|
value: {{ required "A secret key needs to be generated using 'openssl rand -hex 64' and assigned to secretkey." .Values.secretkey }}
|
||||||
|
- name: TRUST_PROXY
|
||||||
|
value: "0"
|
||||||
|
- name: DEFAULT_ADMIN_EMAIL
|
||||||
|
value: {{ .Values.admin_email }}
|
||||||
|
- name: DEFAULT_ADMIN_PASSWORD
|
||||||
|
value: {{ .Values.admin_password }}
|
||||||
|
- name: DEFAULT_ADMIN_NAME
|
||||||
|
value: {{ .Values.admin_name }}
|
||||||
|
- name: DEFAULT_ADMIN_USERNAME
|
||||||
|
value: {{ .Values.admin_username }}
|
||||||
|
{{ range $k, $v := .Values.env }}
|
||||||
|
- name: {{ $k | quote }}
|
||||||
|
value: {{ $v | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.oidc.enabled }}
|
||||||
|
{{- $secretName := default (printf "%s-oidc" (include "planka.fullname" .)) .Values.oidc.existingSecret }}
|
||||||
|
- name: OIDC_CLIENT_ID
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
key: clientId
|
||||||
|
name: {{ $secretName }}
|
||||||
|
- name: OIDC_CLIENT_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
key: clientSecret
|
||||||
|
name: {{ $secretName }}
|
||||||
|
- name: OIDC_ISSUER
|
||||||
|
value: {{ required "issuerUrl is required when configuring OIDC" .Values.oidc.issuerUrl | quote }}
|
||||||
|
- name: OIDC_SCOPES
|
||||||
|
value: {{ join " " .Values.oidc.scopes | default "openid profile email" | quote }}
|
||||||
|
{{- if .Values.oidc.admin.roles }}
|
||||||
|
- name: OIDC_ADMIN_ROLES
|
||||||
|
value: {{ join "," .Values.oidc.admin.roles | quote }}
|
||||||
|
{{- end }}
|
||||||
|
- name: OIDC_ROLES_ATTRIBUTE
|
||||||
|
value: {{ .Values.oidc.admin.rolesAttribute | default "groups" | quote }}
|
||||||
|
{{- if .Values.oidc.admin.ignoreRoles }}
|
||||||
|
- name: OIDC_IGNORE_ROLES
|
||||||
|
value: {{ .Values.oidc.admin.ignoreRoles | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
volumes:
|
||||||
|
- name: planka
|
||||||
|
{{- if .Values.persistence.enabled }}
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ .Values.persistence.existingClaim | default (include "planka.fullname" .) }}
|
||||||
|
{{- else }}
|
||||||
|
emptyDir: {}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{{- if .Values.autoscaling.enabled }}
|
||||||
|
apiVersion: autoscaling/v2beta1
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: {{ include "planka.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: {{ include "planka.fullname" . }}
|
||||||
|
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||||
|
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||||
|
metrics:
|
||||||
|
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: memory
|
||||||
|
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{{- if .Values.ingress.enabled -}}
|
||||||
|
{{- $fullName := include "planka.fullname" . -}}
|
||||||
|
{{- $svcPort := .Values.service.port -}}
|
||||||
|
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||||
|
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||||
|
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||||
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
{{- else -}}
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
{{- end }}
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||||
|
ingressClassName: {{ .Values.ingress.className }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.ingress.tls }}
|
||||||
|
tls:
|
||||||
|
{{- range .Values.ingress.tls }}
|
||||||
|
- hosts:
|
||||||
|
{{- range .hosts }}
|
||||||
|
- {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
secretName: {{ .secretName }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- host: {{ .host | quote }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
{{- range .paths }}
|
||||||
|
- path: {{ .path }}
|
||||||
|
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||||
|
pathType: {{ .pathType }}
|
||||||
|
{{- end }}
|
||||||
|
backend:
|
||||||
|
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||||
|
service:
|
||||||
|
name: {{ $fullName }}
|
||||||
|
port:
|
||||||
|
number: {{ $svcPort }}
|
||||||
|
{{- else }}
|
||||||
|
serviceName: {{ $fullName }}
|
||||||
|
servicePort: {{ $svcPort }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
|
||||||
|
---
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: {{ include "planka.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: {{ include "planka.name" . }}
|
||||||
|
helm.sh/chart: {{ include "planka.chart" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.accessMode }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.size | quote }}
|
||||||
|
{{- if .Values.persistence.storageClass }}
|
||||||
|
{{- if (eq "-" .Values.persistence.storageClass) }}
|
||||||
|
storageClassName: ""
|
||||||
|
{{- else }}
|
||||||
|
storageClassName: "{{ .Values.persistence.storageClass }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{{- if .Values.oidc.enabled }}
|
||||||
|
{{- if eq (and (not (empty .Values.oidc.clientId)) (not (empty .Values.oidc.clientSecret))) (not (empty .Values.oidc.existingSecret)) -}}
|
||||||
|
{{- fail "Either specify inline `clientId` and `clientSecret` or refer to them via `existingSecret`" -}}
|
||||||
|
{{- end }}
|
||||||
|
{{- if (and (and (not (empty .Values.oidc.clientId)) (not (empty .Values.oidc.clientSecret))) (empty .Values.oidc.existingSecret)) -}}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: {{ include "planka.fullname" . }}-oidc
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
clientId: {{ .Values.oidc.clientId | b64enc | quote }}
|
||||||
|
clientSecret: {{ .Values.oidc.clientSecret | b64enc | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "planka.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.port }}
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
{{- include "planka.selectorLabels" . | nindent 4 }}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{{- if .Values.serviceAccount.create -}}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "planka.serviceAccountName" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.serviceAccount.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: "{{ include "planka.fullname" . }}-test-connection"
|
||||||
|
labels:
|
||||||
|
{{- include "planka.labels" . | nindent 4 }}
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": test
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: wget
|
||||||
|
image: busybox
|
||||||
|
command: ['wget']
|
||||||
|
args: ['{{ include "planka.fullname" . }}:{{ .Values.service.port }}']
|
||||||
|
restartPolicy: Never
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
# Default values for planka.
|
||||||
|
# This is a YAML-formatted file.
|
||||||
|
# Declare variables to be passed into your templates.
|
||||||
|
|
||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: ghcr.io/plankanban/planka
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
# Overrides the image tag whose default is the chart appVersion.
|
||||||
|
tag: ""
|
||||||
|
|
||||||
|
imagePullSecrets: []
|
||||||
|
nameOverride: ""
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
# Generate a secret using openssl rand -base64 45
|
||||||
|
secretkey: ""
|
||||||
|
|
||||||
|
# Base url for Planka. Will override `ingress.hosts[0].host`
|
||||||
|
# Defaults to `http://localhost:3000` if ingress is disabled.
|
||||||
|
baseUrl: ""
|
||||||
|
|
||||||
|
serviceAccount:
|
||||||
|
# Specifies whether a service account should be created
|
||||||
|
create: true
|
||||||
|
# Annotations to add to the service account
|
||||||
|
annotations: {}
|
||||||
|
# The name of the service account to use.
|
||||||
|
# If not set and create is true, a name is generated using the fullname template
|
||||||
|
name: ""
|
||||||
|
|
||||||
|
podAnnotations: {}
|
||||||
|
|
||||||
|
podSecurityContext: {}
|
||||||
|
# fsGroup: 2000
|
||||||
|
|
||||||
|
securityContext: {}
|
||||||
|
# capabilities:
|
||||||
|
# drop:
|
||||||
|
# - ALL
|
||||||
|
# readOnlyRootFilesystem: true
|
||||||
|
# runAsNonRoot: true
|
||||||
|
# runAsUser: 1000
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 1337
|
||||||
|
## @param service.containerPort Planka HTTP container port
|
||||||
|
## If empty will default to 1337
|
||||||
|
##
|
||||||
|
containerPort: 1337
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: false
|
||||||
|
className: ""
|
||||||
|
annotations: {}
|
||||||
|
# kubernetes.io/ingress.class: nginx
|
||||||
|
# kubernetes.io/tls-acme: "true"
|
||||||
|
hosts:
|
||||||
|
# Used to set planka BASE_URL if no `baseurl` is provided.
|
||||||
|
- host: planka.local
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
tls: []
|
||||||
|
# - secretName: planka-tls
|
||||||
|
# hosts:
|
||||||
|
# - planka.local
|
||||||
|
|
||||||
|
resources: {}
|
||||||
|
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||||
|
# choice for the user. This also increases chances charts run on environments with little
|
||||||
|
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||||
|
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||||
|
# limits:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
# requests:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
|
||||||
|
autoscaling:
|
||||||
|
enabled: false
|
||||||
|
minReplicas: 1
|
||||||
|
maxReplicas: 100
|
||||||
|
targetCPUUtilizationPercentage: 80
|
||||||
|
# targetMemoryUtilizationPercentage: 80
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
enabled: true
|
||||||
|
auth:
|
||||||
|
database: planka
|
||||||
|
username: planka
|
||||||
|
password: ""
|
||||||
|
postgresPassword: ""
|
||||||
|
replicationPassword: ""
|
||||||
|
# existingSecret: planka-postgresql
|
||||||
|
serviceBindings:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
## PVC-based data storage configuration
|
||||||
|
persistence:
|
||||||
|
enabled: false
|
||||||
|
# existingClaim: netbox-data
|
||||||
|
# storageClass: "-"
|
||||||
|
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
size: 10Gi
|
||||||
|
|
||||||
|
## OpenID Identity Management configuration
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
## ---------------
|
||||||
|
## oidc:
|
||||||
|
## enabled: true
|
||||||
|
## clientId: sxxaAIAxVXlCxTmc1YLHBbQr8NL8MqLI2DUbt42d
|
||||||
|
## clientSecret: om4RTMRVHRszU7bqxB7RZNkHIzA8e4sGYWxeCwIMYQXPwEBWe4SY5a0wwCe9ltB3zrq5f0dnFnp34cEHD7QSMHsKvV9AiV5Z7eqDraMnv0I8IFivmuV5wovAECAYreSI
|
||||||
|
## issuerUrl: https://auth.local/application/o/planka/
|
||||||
|
## admin:
|
||||||
|
## roles:
|
||||||
|
## - planka-admin
|
||||||
|
##
|
||||||
|
## ---------------
|
||||||
|
## NOTE: A minimal configuration requires setting `clientId`, `clientSecret` and `issuerUrl`. (plus `admin.roles` for administrators)
|
||||||
|
## ref: https://docs.planka.cloud/docs/Configuration/OIDC
|
||||||
|
##
|
||||||
|
oidc:
|
||||||
|
## @param oidc.enabled Enable single sign-on (SSO) with OpenID Connect (OIDC)
|
||||||
|
##
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
## OIDC credentials
|
||||||
|
## @param oidc.clientId A string unique to the provider that identifies your app.
|
||||||
|
## @param oidc.clientSecret A secret string that the provider uses to confirm ownership of a client ID.
|
||||||
|
##
|
||||||
|
## NOTE: Either specify inline `clientId` and `clientSecret` or refer to them via `existingSecret`
|
||||||
|
##
|
||||||
|
clientId: ""
|
||||||
|
clientSecret: ""
|
||||||
|
|
||||||
|
## @param oidc.existingSecret Name of an existing secret containing OIDC credentials
|
||||||
|
## NOTE: Must contain key `clientId` and `clientSecret`
|
||||||
|
## NOTE: When it's set, the `clientId` and `clientSecret` parameters are ignored
|
||||||
|
##
|
||||||
|
existingSecret: ""
|
||||||
|
|
||||||
|
## @param oidc.issuerUrl The OpenID connect metadata document endpoint
|
||||||
|
##
|
||||||
|
issuerUrl: ""
|
||||||
|
|
||||||
|
## @param oidc.scopes A list of scopes required for OIDC client.
|
||||||
|
## If empty will default to `openid`, `profile` and `email`
|
||||||
|
## NOTE: Planka needs the email and name claims
|
||||||
|
##
|
||||||
|
scopes: []
|
||||||
|
|
||||||
|
## Admin permissions configuration
|
||||||
|
admin:
|
||||||
|
## @param oidc.admin.ignoreRoles If set to true, the admin roles will be ignored.
|
||||||
|
## It is useful if you want to use OIDC for authentication but not for authorization.
|
||||||
|
## If empty will default to `false`
|
||||||
|
##
|
||||||
|
ignoreRoles: false
|
||||||
|
|
||||||
|
## @param oidc.admin.rolesAttribute The name of a custom group claim that you have configured in your OIDC provider
|
||||||
|
## If empty will default to `groups`
|
||||||
|
##
|
||||||
|
rolesAttribute: groups
|
||||||
|
|
||||||
|
## @param oidc.admin.roles The names of the admin groups
|
||||||
|
##
|
||||||
|
roles: []
|
||||||
|
# - planka-admin
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
REACT_APP_VERSION=1.10.2
|
REACT_APP_VERSION=1.15.5
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ const replaceBaseUrl = (compiler) => {
|
|||||||
if (info.content.indexOf(BASE_URL_PLACEHOLDER) >= 0) {
|
if (info.content.indexOf(BASE_URL_PLACEHOLDER) >= 0) {
|
||||||
if (/\.css$/.exec(info.targetPath)) {
|
if (/\.css$/.exec(info.targetPath)) {
|
||||||
// For CSS 'url(...)' import we can use relative import
|
// For CSS 'url(...)' import we can use relative import
|
||||||
const relPath = path.relative(path.dirname(info.targetPath), info.outputPath);
|
const relPath = path
|
||||||
|
.relative(path.dirname(info.targetPath), info.outputPath)
|
||||||
|
.replace(/\\/g, '/');
|
||||||
replaceInFile(info.targetPath, BASE_URL_PLACEHOLDER, `${relPath}/`);
|
replaceInFile(info.targetPath, BASE_URL_PLACEHOLDER, `${relPath}/`);
|
||||||
} else if (/\.js$/.exec(info.targetPath)) {
|
} else if (/\.js$/.exec(info.targetPath)) {
|
||||||
// For JS 'import ... from "some-asset"' we can get the variable injected in the window object
|
// For JS 'import ... from "some-asset"' we can get the variable injected in the window object
|
||||||
|
|||||||
Generated
+3379
-21947
File diff suppressed because it is too large
Load Diff
+36
-29
@@ -49,65 +49,72 @@
|
|||||||
"**/*.test.js"
|
"**/*.test.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@juggle/resize-observer": "^3.4.0",
|
"@juggle/resize-observer": "^3.4.0",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"date-fns": "^2.29.3",
|
"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": "^22.0.6",
|
"i18next": "^23.7.6",
|
||||||
"i18next-browser-languagedetector": "^7.0.1",
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
"initials": "^3.1.2",
|
"initials": "^3.1.2",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.5",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^4.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"node-sass": "^8.0.0",
|
"nanoid": "^5.0.3",
|
||||||
"photoswipe": "^5.3.3",
|
"node-sass": "^9.0.0",
|
||||||
|
"photoswipe": "^5.4.2",
|
||||||
"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.8.0",
|
"react-datepicker": "^4.21.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-i18next": "^12.0.0",
|
"react-i18next": "^13.5.0",
|
||||||
"react-input-mask": "^2.0.4",
|
"react-input-mask": "^2.0.4",
|
||||||
"react-markdown": "^8.0.3",
|
"react-markdown": "^8.0.7",
|
||||||
"react-photoswipe-gallery": "^2.2.2",
|
"react-photoswipe-gallery": "^2.2.7",
|
||||||
"react-redux": "^8.0.5",
|
"react-redux": "^8.1.3",
|
||||||
"react-router-dom": "^6.4.3",
|
"react-router-dom": "^6.19.0",
|
||||||
"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.4.0",
|
"react-textarea-autosize": "^8.5.3",
|
||||||
"redux": "^4.2.0",
|
"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.1",
|
"redux-saga": "^1.2.3",
|
||||||
"remark-breaks": "^3.0.2",
|
"remark-breaks": "^4.0.0",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"reselect": "^4.1.7",
|
"reselect": "^4.1.8",
|
||||||
"sails.io.js": "^1.2.1",
|
"sails.io.js": "^1.2.1",
|
||||||
"semantic-ui-react": "^2.1.3",
|
"semantic-ui-react": "^2.1.4",
|
||||||
"socket.io-client": "^2.5.0",
|
"socket.io-client": "^2.5.0",
|
||||||
"validator": "^13.7.0",
|
"validator": "^13.11.0",
|
||||||
"whatwg-fetch": "^3.6.2",
|
"whatwg-fetch": "^3.6.19",
|
||||||
"zxcvbn": "^4.4.2"
|
"zxcvbn": "^4.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^6.1.4",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^14.1.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^14.5.1",
|
||||||
"babel-preset-airbnb": "^5.0.0",
|
"babel-preset-airbnb": "^5.0.0",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.10",
|
||||||
"eslint": "^8.28.0",
|
"eslint": "^8.53.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.29.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
"eslint-plugin-react": "^7.31.11",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ const initializeCore = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: with success?
|
||||||
|
initializeCore.fetchConfig = (config) => ({
|
||||||
|
type: ActionTypes.CORE_INITIALIZE__CONFIG_FETCH,
|
||||||
|
payload: {
|
||||||
|
config,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const logout = () => ({
|
const logout = () => ({
|
||||||
type: ActionTypes.LOGOUT,
|
type: ActionTypes.LOGOUT,
|
||||||
payload: {},
|
payload: {},
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import ActionTypes from '../constants/ActionTypes';
|
import ActionTypes from '../constants/ActionTypes';
|
||||||
|
|
||||||
|
const initializeLogin = (config) => ({
|
||||||
|
type: ActionTypes.LOGIN_INITIALIZE,
|
||||||
|
payload: {
|
||||||
|
config,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const authenticate = (data) => ({
|
const authenticate = (data) => ({
|
||||||
type: ActionTypes.AUTHENTICATE,
|
type: ActionTypes.AUTHENTICATE,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -21,12 +28,33 @@ authenticate.failure = (error) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const authenticateUsingOidc = () => ({
|
||||||
|
type: ActionTypes.USING_OIDC_AUTHENTICATE,
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
authenticateUsingOidc.success = (accessToken) => ({
|
||||||
|
type: ActionTypes.USING_OIDC_AUTHENTICATE__SUCCESS,
|
||||||
|
payload: {
|
||||||
|
accessToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
authenticateUsingOidc.failure = (error) => ({
|
||||||
|
type: ActionTypes.USING_OIDC_AUTHENTICATE__FAILURE,
|
||||||
|
payload: {
|
||||||
|
error,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const clearAuthenticateError = () => ({
|
const clearAuthenticateError = () => ({
|
||||||
type: ActionTypes.AUTHENTICATE_ERROR_CLEAR,
|
type: ActionTypes.AUTHENTICATE_ERROR_CLEAR,
|
||||||
payload: {},
|
payload: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
initializeLogin,
|
||||||
authenticate,
|
authenticate,
|
||||||
|
authenticateUsingOidc,
|
||||||
clearAuthenticateError,
|
clearAuthenticateError,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,10 +5,14 @@ import socket from './socket';
|
|||||||
|
|
||||||
const createAccessToken = (data, headers) => http.post('/access-tokens', data, headers);
|
const createAccessToken = (data, headers) => http.post('/access-tokens', data, headers);
|
||||||
|
|
||||||
|
const exchangeForAccessTokenUsingOidc = (data, headers) =>
|
||||||
|
http.post('/access-tokens/exchange-using-oidc', data, headers);
|
||||||
|
|
||||||
const deleteCurrentAccessToken = (headers) =>
|
const deleteCurrentAccessToken = (headers) =>
|
||||||
socket.delete('/access-tokens/me', undefined, headers);
|
socket.delete('/access-tokens/me', undefined, headers);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createAccessToken,
|
createAccessToken,
|
||||||
|
exchangeForAccessTokenUsingOidc,
|
||||||
deleteCurrentAccessToken,
|
deleteCurrentAccessToken,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
import { transformUser } from './users';
|
||||||
|
|
||||||
/* Transformers */
|
/* Transformers */
|
||||||
|
|
||||||
@@ -13,6 +14,10 @@ const getActivities = (cardId, data, headers) =>
|
|||||||
socket.get(`/cards/${cardId}/actions`, data, headers).then((body) => ({
|
socket.get(`/cards/${cardId}/actions`, data, headers).then((body) => ({
|
||||||
...body,
|
...body,
|
||||||
items: body.items.map(transformActivity),
|
items: body.items.map(transformActivity),
|
||||||
|
included: {
|
||||||
|
...body.included,
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/* Event handlers */
|
/* Event handlers */
|
||||||
|
|||||||
@@ -1,18 +1,50 @@
|
|||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
|
||||||
|
/* Transformers */
|
||||||
|
|
||||||
|
export const transformBoardMembership = (boardMembership) => ({
|
||||||
|
...boardMembership,
|
||||||
|
createdAt: new Date(boardMembership.createdAt),
|
||||||
|
});
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
const createBoardMembership = (boardId, data, headers) =>
|
const createBoardMembership = (boardId, data, headers) =>
|
||||||
socket.post(`/boards/${boardId}/memberships`, data, headers);
|
socket.post(`/boards/${boardId}/memberships`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformBoardMembership(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateBoardMembership = (id, data, headers) =>
|
const updateBoardMembership = (id, data, headers) =>
|
||||||
socket.patch(`/board-memberships/${id}`, data, headers);
|
socket.patch(`/board-memberships/${id}`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformBoardMembership(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const deleteBoardMembership = (id, headers) =>
|
const deleteBoardMembership = (id, headers) =>
|
||||||
socket.delete(`/board-memberships/${id}`, undefined, headers);
|
socket.delete(`/board-memberships/${id}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformBoardMembership(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
|
/* Event handlers */
|
||||||
|
|
||||||
|
const makeHandleBoardMembershipCreate = (next) => (body) => {
|
||||||
|
next({
|
||||||
|
...body,
|
||||||
|
item: transformBoardMembership(body.item),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeHandleBoardMembershipUpdate = makeHandleBoardMembershipCreate;
|
||||||
|
|
||||||
|
const makeHandleBoardMembershipDelete = makeHandleBoardMembershipCreate;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createBoardMembership,
|
createBoardMembership,
|
||||||
updateBoardMembership,
|
updateBoardMembership,
|
||||||
deleteBoardMembership,
|
deleteBoardMembership,
|
||||||
|
makeHandleBoardMembershipCreate,
|
||||||
|
makeHandleBoardMembershipUpdate,
|
||||||
|
makeHandleBoardMembershipDelete,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
import http from './http';
|
import http from './http';
|
||||||
|
import { transformUser } from './users';
|
||||||
|
import { transformBoardMembership } from './board-memberships';
|
||||||
import { transformCard } from './cards';
|
import { transformCard } from './cards';
|
||||||
import { transformAttachment } from './attachments';
|
import { transformAttachment } from './attachments';
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
const createBoard = (projectId, data, headers) =>
|
const createBoard = (projectId, data, headers) =>
|
||||||
socket.post(`/projects/${projectId}/boards`, data, headers);
|
socket.post(`/projects/${projectId}/boards`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
included: {
|
||||||
|
...body.included,
|
||||||
|
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const createBoardWithImport = (projectId, data, requestId, headers) =>
|
const createBoardWithImport = (projectId, data, requestId, headers) =>
|
||||||
http.post(`/projects/${projectId}/boards?requestId=${requestId}`, data, headers);
|
http.post(`/projects/${projectId}/boards?requestId=${requestId}`, data, headers);
|
||||||
@@ -18,6 +26,8 @@ const getBoard = (id, subscribe, headers) =>
|
|||||||
...body,
|
...body,
|
||||||
included: {
|
included: {
|
||||||
...body.included,
|
...body.included,
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
|
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||||
cards: body.included.cards.map(transformCard),
|
cards: body.included.cards.map(transformCard),
|
||||||
attachments: body.included.attachments.map(transformAttachment),
|
attachments: body.included.attachments.map(transformAttachment),
|
||||||
},
|
},
|
||||||
|
|||||||
+10
-10
@@ -8,11 +8,11 @@ export const transformCard = (card) => ({
|
|||||||
...(card.dueDate && {
|
...(card.dueDate && {
|
||||||
dueDate: new Date(card.dueDate),
|
dueDate: new Date(card.dueDate),
|
||||||
}),
|
}),
|
||||||
...(card.timer && {
|
...(card.stopwatch && {
|
||||||
timer: {
|
stopwatch: {
|
||||||
...card.timer,
|
...card.stopwatch,
|
||||||
...(card.timer.startedAt && {
|
...(card.stopwatch.startedAt && {
|
||||||
startedAt: new Date(card.timer.startedAt),
|
startedAt: new Date(card.stopwatch.startedAt),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -23,11 +23,11 @@ export const transformCardData = (data) => ({
|
|||||||
...(data.dueDate && {
|
...(data.dueDate && {
|
||||||
dueDate: data.dueDate.toISOString(),
|
dueDate: data.dueDate.toISOString(),
|
||||||
}),
|
}),
|
||||||
...(data.timer && {
|
...(data.stopwatch && {
|
||||||
timer: {
|
stopwatch: {
|
||||||
...data.timer,
|
...data.stopwatch,
|
||||||
...(data.timer.startedAt && {
|
...(data.stopwatch.startedAt && {
|
||||||
startedAt: data.timer.startedAt.toISOString(),
|
startedAt: data.stopwatch.startedAt.toISOString(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import Config from '../constants/Config';
|
|||||||
const http = {};
|
const http = {};
|
||||||
|
|
||||||
// TODO: add all methods
|
// TODO: add all methods
|
||||||
['POST'].forEach((method) => {
|
['GET', 'POST'].forEach((method) => {
|
||||||
http[method.toLowerCase()] = (url, data, headers) => {
|
http[method.toLowerCase()] = (url, data, headers) => {
|
||||||
const formData = Object.keys(data).reduce((result, key) => {
|
const formData =
|
||||||
|
data &&
|
||||||
|
Object.keys(data).reduce((result, key) => {
|
||||||
result.append(key, data[key]);
|
result.append(key, data[key]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import http from './http';
|
import http from './http';
|
||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
import root from './root';
|
||||||
import accessTokens from './access-tokens';
|
import accessTokens from './access-tokens';
|
||||||
import users from './users';
|
import users from './users';
|
||||||
import projects from './projects';
|
import projects from './projects';
|
||||||
@@ -20,6 +21,7 @@ import notifications from './notifications';
|
|||||||
export { http, socket };
|
export { http, socket };
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
...root,
|
||||||
...accessTokens,
|
...accessTokens,
|
||||||
...users,
|
...users,
|
||||||
...projects,
|
...projects,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
|
|
||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
import { transformUser } from './users';
|
||||||
import { transformCard } from './cards';
|
import { transformCard } from './cards';
|
||||||
import { transformActivity } from './activities';
|
import { transformActivity } from './activities';
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ const getNotifications = (headers) =>
|
|||||||
items: body.items.map(transformNotification),
|
items: body.items.map(transformNotification),
|
||||||
included: {
|
included: {
|
||||||
...omit(body.included, 'actions'),
|
...omit(body.included, 'actions'),
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
cards: body.included.cards.map(transformCard),
|
cards: body.included.cards.map(transformCard),
|
||||||
activities: body.included.actions.map(transformActivity),
|
activities: body.included.actions.map(transformActivity),
|
||||||
},
|
},
|
||||||
@@ -30,6 +32,7 @@ const getNotification = (id, headers) =>
|
|||||||
item: transformNotification(body.item),
|
item: transformNotification(body.item),
|
||||||
included: {
|
included: {
|
||||||
...omit(body.included, 'actions'),
|
...omit(body.included, 'actions'),
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
cards: body.included.cards.map(transformCard),
|
cards: body.included.cards.map(transformCard),
|
||||||
activities: body.included.actions.map(transformActivity),
|
activities: body.included.actions.map(transformActivity),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,14 +1,40 @@
|
|||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
|
||||||
|
/* Transformers */
|
||||||
|
|
||||||
|
export const transformProjectManager = (projectManager) => ({
|
||||||
|
...projectManager,
|
||||||
|
createdAt: new Date(projectManager.createdAt),
|
||||||
|
});
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
const createProjectManager = (projectId, data, headers) =>
|
const createProjectManager = (projectId, data, headers) =>
|
||||||
socket.post(`/projects/${projectId}/managers`, data, headers);
|
socket.post(`/projects/${projectId}/managers`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformProjectManager(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const deleteProjectManager = (id, headers) =>
|
const deleteProjectManager = (id, headers) =>
|
||||||
socket.delete(`/project-managers/${id}`, undefined, headers);
|
socket.delete(`/project-managers/${id}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformProjectManager(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
|
/* Event handlers */
|
||||||
|
|
||||||
|
const makeHandleProjectManagerCreate = (next) => (body) => {
|
||||||
|
next({
|
||||||
|
...body,
|
||||||
|
item: transformProjectManager(body.item),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeHandleProjectManagerDelete = makeHandleProjectManagerCreate;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createProjectManager,
|
createProjectManager,
|
||||||
deleteProjectManager,
|
deleteProjectManager,
|
||||||
|
makeHandleProjectManagerCreate,
|
||||||
|
makeHandleProjectManagerDelete,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,41 @@
|
|||||||
import http from './http';
|
import http from './http';
|
||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
import { transformUser } from './users';
|
||||||
|
import { transformProjectManager } from './project-managers';
|
||||||
|
import { transformBoardMembership } from './board-memberships';
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
const getProjects = (headers) => socket.get('/projects', undefined, headers);
|
const getProjects = (headers) =>
|
||||||
|
socket.get('/projects', undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
included: {
|
||||||
|
...body.included,
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
|
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||||
|
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const createProject = (data, headers) => socket.post('/projects', data, headers);
|
const createProject = (data, headers) =>
|
||||||
|
socket.post('/projects', data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
included: {
|
||||||
|
...body.included,
|
||||||
|
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const getProject = (id, headers) => socket.get(`/projects/${id}`, undefined, headers);
|
const getProject = (id, headers) =>
|
||||||
|
socket.get(`/projects/${id}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
included: {
|
||||||
|
...body.included,
|
||||||
|
users: body.included.users.map(transformUser),
|
||||||
|
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||||
|
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers);
|
const updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import http from './http';
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
|
||||||
|
const getConfig = (headers) => http.get('/config', undefined, headers);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getConfig,
|
||||||
|
};
|
||||||
+70
-10
@@ -1,30 +1,87 @@
|
|||||||
import http from './http';
|
import http from './http';
|
||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
|
||||||
|
/* Transformers */
|
||||||
|
|
||||||
|
export const transformUser = (user) => ({
|
||||||
|
...user,
|
||||||
|
createdAt: new Date(user.createdAt),
|
||||||
|
});
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
|
|
||||||
const getUsers = (headers) => socket.get('/users', undefined, headers);
|
const getUsers = (headers) =>
|
||||||
|
socket.get('/users', undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
items: body.items.map(transformUser),
|
||||||
|
}));
|
||||||
|
|
||||||
const createUser = (data, headers) => socket.post('/users', data, headers);
|
const createUser = (data, headers) =>
|
||||||
|
socket.post('/users', data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const getUser = (id, headers) => socket.get(`/users/${id}`, undefined, headers);
|
const getUser = (id, headers) =>
|
||||||
|
socket.get(`/users/${id}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const getCurrentUser = (subscribe, headers) =>
|
const getCurrentUser = (subscribe, headers) =>
|
||||||
socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers);
|
socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers);
|
const updateUser = (id, data, headers) =>
|
||||||
|
socket.patch(`/users/${id}`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateUserEmail = (id, data, headers) => socket.patch(`/users/${id}/email`, data, headers);
|
const updateUserEmail = (id, data, headers) =>
|
||||||
|
socket.patch(`/users/${id}/email`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateUserPassword = (id, data, headers) =>
|
const updateUserPassword = (id, data, headers) =>
|
||||||
socket.patch(`/users/${id}/password`, data, headers);
|
socket.patch(`/users/${id}/password`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateUserUsername = (id, data, headers) =>
|
const updateUserUsername = (id, data, headers) =>
|
||||||
socket.patch(`/users/${id}/username`, data, headers);
|
socket.patch(`/users/${id}/username`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const updateUserAvatar = (id, data, headers) => http.post(`/users/${id}/avatar`, data, headers);
|
const updateUserAvatar = (id, data, headers) =>
|
||||||
|
http.post(`/users/${id}/avatar`, data, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
const deleteUser = (id, headers) => socket.delete(`/users/${id}`, undefined, headers);
|
const deleteUser = (id, headers) =>
|
||||||
|
socket.delete(`/users/${id}`, undefined, headers).then((body) => ({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
}));
|
||||||
|
|
||||||
|
/* Event handlers */
|
||||||
|
|
||||||
|
const makeHandleUserCreate = (next) => (body) => {
|
||||||
|
next({
|
||||||
|
...body,
|
||||||
|
item: transformUser(body.item),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeHandleUserUpdate = makeHandleUserCreate;
|
||||||
|
|
||||||
|
const makeHandleUserDelete = makeHandleUserCreate;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getUsers,
|
getUsers,
|
||||||
@@ -37,4 +94,7 @@ export default {
|
|||||||
updateUserUsername,
|
updateUserUsername,
|
||||||
updateUserAvatar,
|
updateUserAvatar,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
|
makeHandleUserCreate,
|
||||||
|
makeHandleUserUpdate,
|
||||||
|
makeHandleUserDelete,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ const Board = React.memo(
|
|||||||
|
|
||||||
const handleMouseDown = useCallback(
|
const handleMouseDown = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
|
// If button is defined and not equal to 0 (left click)
|
||||||
|
if (event.button) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.target !== wrapper.current && !event.target.dataset.dragScroller) {
|
if (event.target !== wrapper.current && !event.target.dataset.dragScroller) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useSteps } from '../../hooks';
|
|||||||
import BoardMembershipsStep from '../BoardMembershipsStep';
|
import BoardMembershipsStep from '../BoardMembershipsStep';
|
||||||
import LabelsStep from '../LabelsStep';
|
import LabelsStep from '../LabelsStep';
|
||||||
import DueDateEditStep from '../DueDateEditStep';
|
import DueDateEditStep from '../DueDateEditStep';
|
||||||
import TimerEditStep from '../TimerEditStep';
|
import StopwatchEditStep from '../StopwatchEditStep';
|
||||||
import CardMoveStep from '../CardMoveStep';
|
import CardMoveStep from '../CardMoveStep';
|
||||||
import DeleteStep from '../DeleteStep';
|
import DeleteStep from '../DeleteStep';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ const StepTypes = {
|
|||||||
USERS: 'USERS',
|
USERS: 'USERS',
|
||||||
LABELS: 'LABELS',
|
LABELS: 'LABELS',
|
||||||
EDIT_DUE_DATE: 'EDIT_DUE_DATE',
|
EDIT_DUE_DATE: 'EDIT_DUE_DATE',
|
||||||
EDIT_TIMER: 'EDIT_TIMER',
|
EDIT_STOPWATCH: 'EDIT_STOPWATCH',
|
||||||
MOVE: 'MOVE',
|
MOVE: 'MOVE',
|
||||||
DELETE: 'DELETE',
|
DELETE: 'DELETE',
|
||||||
};
|
};
|
||||||
@@ -68,8 +68,8 @@ const ActionsStep = React.memo(
|
|||||||
openStep(StepTypes.EDIT_DUE_DATE);
|
openStep(StepTypes.EDIT_DUE_DATE);
|
||||||
}, [openStep]);
|
}, [openStep]);
|
||||||
|
|
||||||
const handleEditTimerClick = useCallback(() => {
|
const handleEditStopwatchClick = useCallback(() => {
|
||||||
openStep(StepTypes.EDIT_TIMER);
|
openStep(StepTypes.EDIT_STOPWATCH);
|
||||||
}, [openStep]);
|
}, [openStep]);
|
||||||
|
|
||||||
const handleMoveClick = useCallback(() => {
|
const handleMoveClick = useCallback(() => {
|
||||||
@@ -89,10 +89,10 @@ const ActionsStep = React.memo(
|
|||||||
[onUpdate],
|
[onUpdate],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTimerUpdate = useCallback(
|
const handleStopwatchUpdate = useCallback(
|
||||||
(timer) => {
|
(stopwatch) => {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
timer,
|
stopwatch,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[onUpdate],
|
[onUpdate],
|
||||||
@@ -133,11 +133,11 @@ const ActionsStep = React.memo(
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case StepTypes.EDIT_TIMER:
|
case StepTypes.EDIT_STOPWATCH:
|
||||||
return (
|
return (
|
||||||
<TimerEditStep
|
<StopwatchEditStep
|
||||||
defaultValue={card.timer}
|
defaultValue={card.stopwatch}
|
||||||
onUpdate={handleTimerUpdate}
|
onUpdate={handleStopwatchUpdate}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
@@ -197,8 +197,8 @@ const ActionsStep = React.memo(
|
|||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item className={styles.menuItem} onClick={handleEditTimerClick}>
|
<Menu.Item className={styles.menuItem} onClick={handleEditStopwatchClick}>
|
||||||
{t('action.editTimer', {
|
{t('action.editStopwatch', {
|
||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
import { usePopup } from '../../lib/popup';
|
import { usePopup } from '../../lib/popup';
|
||||||
|
|
||||||
import { startTimer, stopTimer } from '../../utils/timer';
|
import { startStopwatch, stopStopwatch } from '../../utils/stopwatch';
|
||||||
import Paths from '../../constants/Paths';
|
import Paths from '../../constants/Paths';
|
||||||
import Tasks from './Tasks';
|
import Tasks from './Tasks';
|
||||||
import NameEdit from './NameEdit';
|
import NameEdit from './NameEdit';
|
||||||
@@ -14,7 +14,7 @@ import ActionsStep from './ActionsStep';
|
|||||||
import User from '../User';
|
import User from '../User';
|
||||||
import Label from '../Label';
|
import Label from '../Label';
|
||||||
import DueDate from '../DueDate';
|
import DueDate from '../DueDate';
|
||||||
import Timer from '../Timer';
|
import Stopwatch from '../Stopwatch';
|
||||||
|
|
||||||
import styles from './Card.module.scss';
|
import styles from './Card.module.scss';
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ const Card = React.memo(
|
|||||||
index,
|
index,
|
||||||
name,
|
name,
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
coverUrl,
|
coverUrl,
|
||||||
boardId,
|
boardId,
|
||||||
listId,
|
listId,
|
||||||
@@ -60,15 +60,15 @@ const Card = React.memo(
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleToggleTimerClick = useCallback(
|
const handleToggleStopwatchClick = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
timer: timer.startedAt ? stopTimer(timer) : startTimer(timer),
|
stopwatch: stopwatch.startedAt ? stopStopwatch(stopwatch) : startStopwatch(stopwatch),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[timer, onUpdate],
|
[stopwatch, onUpdate],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNameUpdate = useCallback(
|
const handleNameUpdate = useCallback(
|
||||||
@@ -104,7 +104,7 @@ const Card = React.memo(
|
|||||||
)}
|
)}
|
||||||
<div className={styles.name}>{name}</div>
|
<div className={styles.name}>{name}</div>
|
||||||
{tasks.length > 0 && <Tasks items={tasks} />}
|
{tasks.length > 0 && <Tasks items={tasks} />}
|
||||||
{(dueDate || timer || notificationsTotal > 0) && (
|
{(dueDate || stopwatch || notificationsTotal > 0) && (
|
||||||
<span className={styles.attachments}>
|
<span className={styles.attachments}>
|
||||||
{notificationsTotal > 0 && (
|
{notificationsTotal > 0 && (
|
||||||
<span
|
<span
|
||||||
@@ -122,14 +122,14 @@ const Card = React.memo(
|
|||||||
<DueDate value={dueDate} size="tiny" />
|
<DueDate value={dueDate} size="tiny" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{timer && (
|
{stopwatch && (
|
||||||
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
||||||
<Timer
|
<Stopwatch
|
||||||
as="span"
|
as="span"
|
||||||
startedAt={timer.startedAt}
|
startedAt={stopwatch.startedAt}
|
||||||
total={timer.total}
|
total={stopwatch.total}
|
||||||
size="tiny"
|
size="tiny"
|
||||||
onClick={canEdit ? handleToggleTimerClick : undefined}
|
onClick={canEdit ? handleToggleStopwatchClick : undefined}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -171,7 +171,7 @@ const Card = React.memo(
|
|||||||
<ActionsPopup
|
<ActionsPopup
|
||||||
card={{
|
card={{
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
boardId,
|
boardId,
|
||||||
listId,
|
listId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -219,7 +219,7 @@ Card.propTypes = {
|
|||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
dueDate: PropTypes.instanceOf(Date),
|
dueDate: PropTypes.instanceOf(Date),
|
||||||
timer: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
stopwatch: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
coverUrl: PropTypes.string,
|
coverUrl: PropTypes.string,
|
||||||
boardId: PropTypes.string.isRequired,
|
boardId: PropTypes.string.isRequired,
|
||||||
listId: PropTypes.string.isRequired,
|
listId: PropTypes.string.isRequired,
|
||||||
@@ -252,7 +252,7 @@ Card.propTypes = {
|
|||||||
|
|
||||||
Card.defaultProps = {
|
Card.defaultProps = {
|
||||||
dueDate: undefined,
|
dueDate: undefined,
|
||||||
timer: undefined,
|
stopwatch: undefined,
|
||||||
coverUrl: undefined,
|
coverUrl: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||||
|
|
||||||
import { useClosableForm, useForm } from '../../../hooks';
|
import { useForm } from '../../../hooks';
|
||||||
|
|
||||||
import styles from './CommentEdit.module.scss';
|
import styles from './CommentEdit.module.scss';
|
||||||
|
|
||||||
@@ -35,12 +35,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
|
|||||||
text: data.text.trim(),
|
text: data.text.trim(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!cleanData.text) {
|
if (cleanData.text && !dequal(cleanData, defaultData)) {
|
||||||
textField.current.ref.current.select();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dequal(cleanData, defaultData)) {
|
|
||||||
onUpdate(cleanData);
|
onUpdate(cleanData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,10 +60,9 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
|
|||||||
[submit],
|
[submit],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
const handleFieldBlur = useCallback(() => {
|
||||||
close,
|
submit();
|
||||||
isOpened,
|
}, [submit]);
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
submit();
|
submit();
|
||||||
@@ -99,13 +93,7 @@ const CommentEdit = React.forwardRef(({ children, defaultData, onUpdate }, ref)
|
|||||||
onBlur={handleFieldBlur}
|
onBlur={handleFieldBlur}
|
||||||
/>
|
/>
|
||||||
<div className={styles.controls}>
|
<div className={styles.controls}>
|
||||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
<Button positive content={t('action.save')} />
|
||||||
<Button
|
|
||||||
positive
|
|
||||||
content={t('action.save')}
|
|
||||||
onMouseOver={handleControlMouseOver}
|
|
||||||
onMouseOut={handleControlMouseOut}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import classNames from 'classnames';
|
|||||||
import { useTranslation, Trans } from 'react-i18next';
|
import { useTranslation, Trans } from 'react-i18next';
|
||||||
import { Comment } from 'semantic-ui-react';
|
import { Comment } from 'semantic-ui-react';
|
||||||
|
|
||||||
|
import getDateFormat from '../../../utils/get-date-format';
|
||||||
import { ActivityTypes } from '../../../constants/Enums';
|
import { ActivityTypes } from '../../../constants/Enums';
|
||||||
import ItemComment from './ItemComment';
|
import ItemComment from './ItemComment';
|
||||||
import User from '../../User';
|
import User from '../../User';
|
||||||
@@ -66,7 +67,7 @@ const Item = React.memo(({ type, data, createdAt, user }) => {
|
|||||||
<div className={classNames(styles.content)}>
|
<div className={classNames(styles.content)}>
|
||||||
<div>{contentNode}</div>
|
<div>{contentNode}</div>
|
||||||
<span className={styles.date}>
|
<span className={styles.date}>
|
||||||
{t('format:longDateTime', {
|
{t(`format:${getDateFormat(createdAt)}`, {
|
||||||
postProcess: 'formatDate',
|
postProcess: 'formatDate',
|
||||||
value: createdAt,
|
value: createdAt,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Comment } from 'semantic-ui-react';
|
|||||||
import { usePopup } from '../../../lib/popup';
|
import { usePopup } from '../../../lib/popup';
|
||||||
import { Markdown } from '../../../lib/custom-ui';
|
import { Markdown } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
|
import getDateFormat from '../../../utils/get-date-format';
|
||||||
import CommentEdit from './CommentEdit';
|
import CommentEdit from './CommentEdit';
|
||||||
import User from '../../User';
|
import User from '../../User';
|
||||||
import DeleteStep from '../../DeleteStep';
|
import DeleteStep from '../../DeleteStep';
|
||||||
@@ -33,7 +34,7 @@ const ItemComment = React.memo(
|
|||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
<span className={styles.author}>{user.name}</span>
|
<span className={styles.author}>{user.name}</span>
|
||||||
<span className={styles.date}>
|
<span className={styles.date}>
|
||||||
{t('format:longDateTime', {
|
{t(`format:${getDateFormat(createdAt)}`, {
|
||||||
postProcess: 'formatDate',
|
postProcess: 'formatDate',
|
||||||
value: createdAt,
|
value: createdAt,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Button, Grid, Icon, Modal } from 'semantic-ui-react';
|
|||||||
import { usePopup } from '../../lib/popup';
|
import { usePopup } from '../../lib/popup';
|
||||||
import { Markdown } from '../../lib/custom-ui';
|
import { Markdown } from '../../lib/custom-ui';
|
||||||
|
|
||||||
import { startTimer, stopTimer } from '../../utils/timer';
|
import { startStopwatch, stopStopwatch } from '../../utils/stopwatch';
|
||||||
import NameField from './NameField';
|
import NameField from './NameField';
|
||||||
import DescriptionEdit from './DescriptionEdit';
|
import DescriptionEdit from './DescriptionEdit';
|
||||||
import Tasks from './Tasks';
|
import Tasks from './Tasks';
|
||||||
@@ -17,11 +17,11 @@ import Activities from './Activities';
|
|||||||
import User from '../User';
|
import User from '../User';
|
||||||
import Label from '../Label';
|
import Label from '../Label';
|
||||||
import DueDate from '../DueDate';
|
import DueDate from '../DueDate';
|
||||||
import Timer from '../Timer';
|
import Stopwatch from '../Stopwatch';
|
||||||
import BoardMembershipsStep from '../BoardMembershipsStep';
|
import BoardMembershipsStep from '../BoardMembershipsStep';
|
||||||
import LabelsStep from '../LabelsStep';
|
import LabelsStep from '../LabelsStep';
|
||||||
import DueDateEditStep from '../DueDateEditStep';
|
import DueDateEditStep from '../DueDateEditStep';
|
||||||
import TimerEditStep from '../TimerEditStep';
|
import StopwatchEditStep from '../StopwatchEditStep';
|
||||||
import CardMoveStep from '../CardMoveStep';
|
import CardMoveStep from '../CardMoveStep';
|
||||||
import DeleteStep from '../DeleteStep';
|
import DeleteStep from '../DeleteStep';
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ const CardModal = React.memo(
|
|||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
isActivitiesFetching,
|
isActivitiesFetching,
|
||||||
isAllActivitiesFetched,
|
isAllActivitiesFetched,
|
||||||
@@ -83,11 +83,11 @@ const CardModal = React.memo(
|
|||||||
|
|
||||||
const isGalleryOpened = useRef(false);
|
const isGalleryOpened = useRef(false);
|
||||||
|
|
||||||
const handleToggleTimerClick = useCallback(() => {
|
const handleToggleStopwatchClick = useCallback(() => {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
timer: timer.startedAt ? stopTimer(timer) : startTimer(timer),
|
stopwatch: stopwatch.startedAt ? stopStopwatch(stopwatch) : startStopwatch(stopwatch),
|
||||||
});
|
});
|
||||||
}, [timer, onUpdate]);
|
}, [stopwatch, onUpdate]);
|
||||||
|
|
||||||
const handleNameUpdate = useCallback(
|
const handleNameUpdate = useCallback(
|
||||||
(newName) => {
|
(newName) => {
|
||||||
@@ -116,10 +116,10 @@ const CardModal = React.memo(
|
|||||||
[onUpdate],
|
[onUpdate],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTimerUpdate = useCallback(
|
const handleStopwatchUpdate = useCallback(
|
||||||
(newTimer) => {
|
(newStopwatch) => {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
timer: newTimer,
|
stopwatch: newStopwatch,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[onUpdate],
|
[onUpdate],
|
||||||
@@ -160,7 +160,7 @@ const CardModal = React.memo(
|
|||||||
const BoardMembershipsPopup = usePopup(BoardMembershipsStep);
|
const BoardMembershipsPopup = usePopup(BoardMembershipsStep);
|
||||||
const LabelsPopup = usePopup(LabelsStep);
|
const LabelsPopup = usePopup(LabelsStep);
|
||||||
const DueDateEditPopup = usePopup(DueDateEditStep);
|
const DueDateEditPopup = usePopup(DueDateEditStep);
|
||||||
const TimerEditPopup = usePopup(TimerEditStep);
|
const StopwatchEditPopup = usePopup(StopwatchEditStep);
|
||||||
const CardMovePopup = usePopup(CardMoveStep);
|
const CardMovePopup = usePopup(CardMoveStep);
|
||||||
const DeletePopup = usePopup(DeleteStep);
|
const DeletePopup = usePopup(DeleteStep);
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ const CardModal = React.memo(
|
|||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
<Grid.Row className={styles.modalPadding}>
|
<Grid.Row className={styles.modalPadding}>
|
||||||
<Grid.Column width={canEdit ? 12 : 16} className={styles.contentPadding}>
|
<Grid.Column width={canEdit ? 12 : 16} className={styles.contentPadding}>
|
||||||
{(users.length > 0 || labels.length > 0 || dueDate || timer) && (
|
{(users.length > 0 || labels.length > 0 || dueDate || stopwatch) && (
|
||||||
<div className={styles.moduleWrapper}>
|
<div className={styles.moduleWrapper}>
|
||||||
{users.length > 0 && (
|
{users.length > 0 && (
|
||||||
<div className={styles.attachments}>
|
<div className={styles.attachments}>
|
||||||
@@ -217,6 +217,7 @@ const CardModal = React.memo(
|
|||||||
onUserSelect={onUserAdd}
|
onUserSelect={onUserAdd}
|
||||||
onUserDeselect={onUserRemove}
|
onUserDeselect={onUserRemove}
|
||||||
>
|
>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames(styles.attachment, styles.dueDate)}
|
className={classNames(styles.attachment, styles.dueDate)}
|
||||||
@@ -266,6 +267,7 @@ const CardModal = React.memo(
|
|||||||
onMove={onLabelMove}
|
onMove={onLabelMove}
|
||||||
onDelete={onLabelDelete}
|
onDelete={onLabelDelete}
|
||||||
>
|
>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames(styles.attachment, styles.dueDate)}
|
className={classNames(styles.attachment, styles.dueDate)}
|
||||||
@@ -294,30 +296,34 @@ const CardModal = React.memo(
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{timer && (
|
{stopwatch && (
|
||||||
<div className={styles.attachments}>
|
<div className={styles.attachments}>
|
||||||
<div className={styles.text}>
|
<div className={styles.text}>
|
||||||
{t('common.timer', {
|
{t('common.stopwatch', {
|
||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.attachment}>
|
<span className={styles.attachment}>
|
||||||
{canEdit ? (
|
{canEdit ? (
|
||||||
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
|
<StopwatchEditPopup
|
||||||
<Timer startedAt={timer.startedAt} total={timer.total} />
|
defaultValue={stopwatch}
|
||||||
</TimerEditPopup>
|
onUpdate={handleStopwatchUpdate}
|
||||||
|
>
|
||||||
|
<Stopwatch startedAt={stopwatch.startedAt} total={stopwatch.total} />
|
||||||
|
</StopwatchEditPopup>
|
||||||
) : (
|
) : (
|
||||||
<Timer startedAt={timer.startedAt} total={timer.total} />
|
<Stopwatch startedAt={stopwatch.startedAt} total={stopwatch.total} />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
|
// eslint-disable-next-line jsx-a11y/control-has-associated-label
|
||||||
<button
|
<button
|
||||||
onClick={handleToggleTimerClick}
|
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames(styles.attachment, styles.dueDate)}
|
className={classNames(styles.attachment, styles.dueDate)}
|
||||||
|
onClick={handleToggleStopwatchClick}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name={timer.startedAt ? 'pause' : 'play'}
|
name={stopwatch.startedAt ? 'pause' : 'play'}
|
||||||
size="small"
|
size="small"
|
||||||
className={styles.addAttachment}
|
className={styles.addAttachment}
|
||||||
/>
|
/>
|
||||||
@@ -447,12 +453,12 @@ const CardModal = React.memo(
|
|||||||
})}
|
})}
|
||||||
</Button>
|
</Button>
|
||||||
</DueDateEditPopup>
|
</DueDateEditPopup>
|
||||||
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
|
<StopwatchEditPopup defaultValue={stopwatch} onUpdate={handleStopwatchUpdate}>
|
||||||
<Button fluid className={styles.actionButton}>
|
<Button fluid className={styles.actionButton}>
|
||||||
<Icon name="clock outline" className={styles.actionIcon} />
|
<Icon name="clock outline" className={styles.actionIcon} />
|
||||||
{t('common.timer')}
|
{t('common.stopwatch')}
|
||||||
</Button>
|
</Button>
|
||||||
</TimerEditPopup>
|
</StopwatchEditPopup>
|
||||||
<AttachmentAddPopup onCreate={onAttachmentCreate}>
|
<AttachmentAddPopup onCreate={onAttachmentCreate}>
|
||||||
<Button fluid className={styles.actionButton}>
|
<Button fluid className={styles.actionButton}>
|
||||||
<Icon name="attach" className={styles.actionIcon} />
|
<Icon name="attach" className={styles.actionIcon} />
|
||||||
@@ -524,7 +530,7 @@ CardModal.propTypes = {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
dueDate: PropTypes.instanceOf(Date),
|
dueDate: PropTypes.instanceOf(Date),
|
||||||
timer: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
stopwatch: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
isSubscribed: PropTypes.bool.isRequired,
|
isSubscribed: PropTypes.bool.isRequired,
|
||||||
isActivitiesFetching: PropTypes.bool.isRequired,
|
isActivitiesFetching: PropTypes.bool.isRequired,
|
||||||
isAllActivitiesFetched: PropTypes.bool.isRequired,
|
isAllActivitiesFetched: PropTypes.bool.isRequired,
|
||||||
@@ -577,7 +583,7 @@ CardModal.propTypes = {
|
|||||||
CardModal.defaultProps = {
|
CardModal.defaultProps = {
|
||||||
description: undefined,
|
description: undefined,
|
||||||
dueDate: undefined,
|
dueDate: undefined,
|
||||||
timer: undefined,
|
stopwatch: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CardModal;
|
export default CardModal;
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ const DescriptionEdit = React.forwardRef(({ children, defaultValue, onUpdate },
|
|||||||
onChange={setValue}
|
onChange={setValue}
|
||||||
/>
|
/>
|
||||||
<div className={styles.controls}>
|
<div className={styles.controls}>
|
||||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
|
||||||
<Button positive content={t('action.save')} />
|
<Button positive content={t('action.save')} />
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||||
|
|
||||||
import { useClosableForm, useField } from '../../../hooks';
|
import { useField } from '../../../hooks';
|
||||||
|
|
||||||
import styles from './NameEdit.module.scss';
|
import styles from './NameEdit.module.scss';
|
||||||
|
|
||||||
@@ -28,12 +28,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
|||||||
const submit = useCallback(() => {
|
const submit = useCallback(() => {
|
||||||
const cleanValue = value.trim();
|
const cleanValue = value.trim();
|
||||||
|
|
||||||
if (!cleanValue) {
|
if (cleanValue && cleanValue !== defaultValue) {
|
||||||
field.current.ref.current.select();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanValue !== defaultValue) {
|
|
||||||
onUpdate(cleanValue);
|
onUpdate(cleanValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,10 +55,9 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
|||||||
[submit],
|
[submit],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
const handleFieldBlur = useCallback(() => {
|
||||||
close,
|
submit();
|
||||||
isOpened,
|
}, [submit]);
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
submit();
|
submit();
|
||||||
@@ -93,13 +87,7 @@ const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) =>
|
|||||||
onBlur={handleFieldBlur}
|
onBlur={handleFieldBlur}
|
||||||
/>
|
/>
|
||||||
<div className={styles.controls}>
|
<div className={styles.controls}>
|
||||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
<Button positive content={t('action.save')} />
|
||||||
<Button
|
|
||||||
positive
|
|
||||||
content={t('action.save')}
|
|
||||||
onMouseOver={handleControlMouseOver}
|
|
||||||
onMouseOut={handleControlMouseOut}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ const Tasks = React.memo(({ items, canEdit, onCreate, onUpdate, onMove, onDelete
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{items.length > 0 && (
|
{items.length > 0 && (
|
||||||
|
<>
|
||||||
|
<span className={styles.progressWrapper}>
|
||||||
<Progress
|
<Progress
|
||||||
autoSuccess
|
autoSuccess
|
||||||
value={completedItems.length}
|
value={completedItems.length}
|
||||||
@@ -56,6 +58,11 @@ const Tasks = React.memo(({ items, canEdit, onCreate, onUpdate, onMove, onDelete
|
|||||||
size="tiny"
|
size="tiny"
|
||||||
className={styles.progress}
|
className={styles.progress}
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
|
<span className={styles.count}>
|
||||||
|
{completedItems.length}/{items.length}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||||
<Droppable droppableId="tasks" type={DroppableTypes.TASK}>
|
<Droppable droppableId="tasks" type={DroppableTypes.TASK}>
|
||||||
|
|||||||
@@ -3,6 +3,23 @@
|
|||||||
margin: 0 0 16px;
|
margin: 0 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progressWrapper {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 0;
|
||||||
|
vertical-align: top;
|
||||||
|
width: calc(100% - 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
color: #8c8c8c;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 14px;
|
||||||
|
text-align: right;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
.taskButton {
|
.taskButton {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import getDateFormat from '../../utils/get-date-format';
|
||||||
|
|
||||||
import styles from './DueDate.module.scss';
|
import styles from './DueDate.module.scss';
|
||||||
|
|
||||||
const SIZES = {
|
const SIZES = {
|
||||||
@@ -12,15 +14,27 @@ const SIZES = {
|
|||||||
MEDIUM: 'medium',
|
MEDIUM: 'medium',
|
||||||
};
|
};
|
||||||
|
|
||||||
const FORMATS = {
|
const LONG_DATE_FORMAT_BY_SIZE = {
|
||||||
tiny: 'longDate',
|
tiny: 'longDate',
|
||||||
small: 'longDate',
|
small: 'longDate',
|
||||||
medium: 'longDateTime',
|
medium: 'longDateTime',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FULL_DATE_FORMAT_BY_SIZE = {
|
||||||
|
tiny: 'fullDate',
|
||||||
|
small: 'fullDate',
|
||||||
|
medium: 'fullDateTime',
|
||||||
|
};
|
||||||
|
|
||||||
const DueDate = React.memo(({ value, size, isDisabled, onClick }) => {
|
const DueDate = React.memo(({ value, size, isDisabled, onClick }) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
const dateFormat = getDateFormat(
|
||||||
|
value,
|
||||||
|
LONG_DATE_FORMAT_BY_SIZE[size],
|
||||||
|
FULL_DATE_FORMAT_BY_SIZE[size],
|
||||||
|
);
|
||||||
|
|
||||||
const contentNode = (
|
const contentNode = (
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@@ -29,7 +43,7 @@ const DueDate = React.memo(({ value, size, isDisabled, onClick }) => {
|
|||||||
onClick && styles.wrapperHoverable,
|
onClick && styles.wrapperHoverable,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{t(`format:${FORMATS[size]}`, {
|
{t(`format:${dateFormat}`, {
|
||||||
value,
|
value,
|
||||||
postProcess: 'formatDate',
|
postProcess: 'formatDate',
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Icon, Menu } from 'semantic-ui-react';
|
import { Button, Icon, Menu } from 'semantic-ui-react';
|
||||||
import { usePopup } from '../../lib/popup';
|
import { usePopup } from '../../lib/popup';
|
||||||
|
|
||||||
import Paths from '../../constants/Paths';
|
import Paths from '../../constants/Paths';
|
||||||
@@ -55,15 +55,16 @@ const Header = React.memo(
|
|||||||
>
|
>
|
||||||
<Icon fitted name="arrow left" />
|
<Icon fitted name="arrow left" />
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item className={classNames(styles.item, styles.title)}>
|
||||||
className={classNames(
|
{project.name}
|
||||||
styles.item,
|
{canEditProject && (
|
||||||
canEditProject && styles.itemHoverable,
|
<Button
|
||||||
styles.title,
|
className={classNames(styles.editButton, styles.target)}
|
||||||
)}
|
|
||||||
onClick={handleProjectSettingsClick}
|
onClick={handleProjectSettingsClick}
|
||||||
>
|
>
|
||||||
{project.name}
|
<Icon fitted name="pencil" size="small" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.Menu>
|
</Menu.Menu>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,20 @@
|
|||||||
:global(#app) {
|
:global(#app) {
|
||||||
|
.editButton {
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 34px;
|
||||||
|
margin-left: 8px;
|
||||||
|
opacity: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 34px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
user-select: auto;
|
user-select: auto;
|
||||||
@@ -11,6 +27,10 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
|
||||||
|
.target {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Form, Grid, Header, Message } from 'semantic-ui-react';
|
import { Button, Form, Grid, Header, Message } from 'semantic-ui-react';
|
||||||
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
|
import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
|
||||||
import { Input } from '../../lib/custom-ui';
|
import { Input } from '../../lib/custom-ui';
|
||||||
|
|
||||||
@@ -28,6 +28,21 @@ const createMessage = (error) => {
|
|||||||
type: 'error',
|
type: 'error',
|
||||||
content: 'common.invalidPassword',
|
content: 'common.invalidPassword',
|
||||||
};
|
};
|
||||||
|
case 'Use single sign-on':
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
content: 'common.useSingleSignOn',
|
||||||
|
};
|
||||||
|
case 'Email already in use':
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
content: 'common.emailAlreadyInUse',
|
||||||
|
};
|
||||||
|
case 'Username already in use':
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
content: 'common.usernameAlreadyInUse',
|
||||||
|
};
|
||||||
case 'Failed to fetch':
|
case 'Failed to fetch':
|
||||||
return {
|
return {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
@@ -47,7 +62,16 @@ const createMessage = (error) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Login = React.memo(
|
const Login = React.memo(
|
||||||
({ defaultData, isSubmitting, error, onAuthenticate, onMessageDismiss }) => {
|
({
|
||||||
|
defaultData,
|
||||||
|
isSubmitting,
|
||||||
|
isSubmittingUsingOidc,
|
||||||
|
error,
|
||||||
|
withOidc,
|
||||||
|
onAuthenticate,
|
||||||
|
onAuthenticateUsingOidc,
|
||||||
|
onMessageDismiss,
|
||||||
|
}) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const wasSubmitting = usePrevious(isSubmitting);
|
const wasSubmitting = usePrevious(isSubmitting);
|
||||||
|
|
||||||
@@ -168,9 +192,19 @@ const Login = React.memo(
|
|||||||
content={t('action.logIn')}
|
content={t('action.logIn')}
|
||||||
floated="right"
|
floated="right"
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting || isSubmittingUsingOidc}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
{withOidc && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
loading={isSubmittingUsingOidc}
|
||||||
|
disabled={isSubmitting || isSubmittingUsingOidc}
|
||||||
|
onClick={onAuthenticateUsingOidc}
|
||||||
|
>
|
||||||
|
{t('action.logInWithSSO')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
@@ -201,10 +235,15 @@ const Login = React.memo(
|
|||||||
);
|
);
|
||||||
|
|
||||||
Login.propTypes = {
|
Login.propTypes = {
|
||||||
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
/* eslint-disable react/forbid-prop-types */
|
||||||
|
defaultData: PropTypes.object.isRequired,
|
||||||
|
/* eslint-enable react/forbid-prop-types */
|
||||||
isSubmitting: PropTypes.bool.isRequired,
|
isSubmitting: 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,
|
||||||
onAuthenticate: PropTypes.func.isRequired,
|
onAuthenticate: PropTypes.func.isRequired,
|
||||||
|
onAuthenticateUsingOidc: PropTypes.func.isRequired,
|
||||||
onMessageDismiss: PropTypes.func.isRequired,
|
onMessageDismiss: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Loader } from 'semantic-ui-react';
|
||||||
|
|
||||||
|
import LoginContainer from '../containers/LoginContainer';
|
||||||
|
|
||||||
|
const LoginWrapper = React.memo(({ isInitializing }) => {
|
||||||
|
if (isInitializing) {
|
||||||
|
return <Loader active size="massive" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <LoginContainer />;
|
||||||
|
});
|
||||||
|
|
||||||
|
LoginWrapper.propTypes = {
|
||||||
|
isInitializing: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginWrapper;
|
||||||
@@ -5,7 +5,7 @@ import { Route, Routes } from 'react-router-dom';
|
|||||||
import { ReduxRouter } from '../lib/redux-router';
|
import { ReduxRouter } from '../lib/redux-router';
|
||||||
|
|
||||||
import Paths from '../constants/Paths';
|
import Paths from '../constants/Paths';
|
||||||
import LoginContainer from '../containers/LoginContainer';
|
import LoginWrapperContainer from '../containers/LoginWrapperContainer';
|
||||||
import CoreContainer from '../containers/CoreContainer';
|
import CoreContainer from '../containers/CoreContainer';
|
||||||
import NotFound from './NotFound';
|
import NotFound from './NotFound';
|
||||||
|
|
||||||
@@ -13,7 +13,6 @@ import 'react-datepicker/dist/react-datepicker.css';
|
|||||||
import 'photoswipe/dist/photoswipe.css';
|
import 'photoswipe/dist/photoswipe.css';
|
||||||
import 'easymde/dist/easymde.min.css';
|
import 'easymde/dist/easymde.min.css';
|
||||||
import '../lib/custom-ui/styles.css';
|
import '../lib/custom-ui/styles.css';
|
||||||
|
|
||||||
import '../styles.module.scss';
|
import '../styles.module.scss';
|
||||||
|
|
||||||
function Root({ store, history }) {
|
function Root({ store, history }) {
|
||||||
@@ -21,7 +20,8 @@ function Root({ store, history }) {
|
|||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ReduxRouter history={history}>
|
<ReduxRouter history={history}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={Paths.LOGIN} element={<LoginContainer />} />
|
<Route path={Paths.LOGIN} element={<LoginWrapperContainer />} />
|
||||||
|
<Route path={Paths.OIDC_CALLBACK} element={<LoginWrapperContainer />} />
|
||||||
<Route path={Paths.ROOT} element={<CoreContainer />} />
|
<Route path={Paths.ROOT} element={<CoreContainer />} />
|
||||||
<Route path={Paths.PROJECTS} element={<CoreContainer />} />
|
<Route path={Paths.PROJECTS} element={<CoreContainer />} />
|
||||||
<Route path={Paths.BOARDS} element={<CoreContainer />} />
|
<Route path={Paths.BOARDS} element={<CoreContainer />} />
|
||||||
|
|||||||
+7
-7
@@ -4,9 +4,9 @@ import PropTypes from 'prop-types';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useForceUpdate, usePrevious } from '../../lib/hooks';
|
import { useForceUpdate, usePrevious } from '../../lib/hooks';
|
||||||
|
|
||||||
import { formatTimer } from '../../utils/timer';
|
import { formatStopwatch } from '../../utils/stopwatch';
|
||||||
|
|
||||||
import styles from './Timer.module.scss';
|
import styles from './Stopwatch.module.scss';
|
||||||
|
|
||||||
const SIZES = {
|
const SIZES = {
|
||||||
TINY: 'tiny',
|
TINY: 'tiny',
|
||||||
@@ -14,7 +14,7 @@ const SIZES = {
|
|||||||
MEDIUM: 'medium',
|
MEDIUM: 'medium',
|
||||||
};
|
};
|
||||||
|
|
||||||
const Timer = React.memo(({ as, startedAt, total, size, isDisabled, onClick }) => {
|
const Stopwatch = React.memo(({ as, startedAt, total, size, isDisabled, onClick }) => {
|
||||||
const prevStartedAt = usePrevious(startedAt);
|
const prevStartedAt = usePrevious(startedAt);
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ const Timer = React.memo(({ as, startedAt, total, size, isDisabled, onClick }) =
|
|||||||
onClick && styles.wrapperHoverable,
|
onClick && styles.wrapperHoverable,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{formatTimer({ startedAt, total })}
|
{formatStopwatch({ startedAt, total })}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ const Timer = React.memo(({ as, startedAt, total, size, isDisabled, onClick }) =
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Timer.propTypes = {
|
Stopwatch.propTypes = {
|
||||||
as: PropTypes.elementType,
|
as: PropTypes.elementType,
|
||||||
startedAt: PropTypes.instanceOf(Date),
|
startedAt: PropTypes.instanceOf(Date),
|
||||||
total: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types
|
total: PropTypes.number.isRequired, // eslint-disable-line react/no-unused-prop-types
|
||||||
@@ -80,7 +80,7 @@ Timer.propTypes = {
|
|||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
Timer.defaultProps = {
|
Stopwatch.defaultProps = {
|
||||||
as: 'button',
|
as: 'button',
|
||||||
startedAt: undefined,
|
startedAt: undefined,
|
||||||
size: SIZES.MEDIUM,
|
size: SIZES.MEDIUM,
|
||||||
@@ -88,4 +88,4 @@ Timer.defaultProps = {
|
|||||||
onClick: undefined,
|
onClick: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Timer;
|
export default Stopwatch;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Stopwatch from './Stopwatch';
|
||||||
|
|
||||||
|
export default Stopwatch;
|
||||||
+21
-15
@@ -7,12 +7,18 @@ import { useToggle } from '../../lib/hooks';
|
|||||||
import { Input, Popup } from '../../lib/custom-ui';
|
import { Input, Popup } from '../../lib/custom-ui';
|
||||||
|
|
||||||
import { useForm } from '../../hooks';
|
import { useForm } from '../../hooks';
|
||||||
import { createTimer, getTimerParts, startTimer, stopTimer, updateTimer } from '../../utils/timer';
|
import {
|
||||||
|
createStopwatch,
|
||||||
|
getStopwatchParts,
|
||||||
|
startStopwatch,
|
||||||
|
stopStopwatch,
|
||||||
|
updateStopwatch,
|
||||||
|
} from '../../utils/stopwatch';
|
||||||
|
|
||||||
import styles from './TimerEditStep.module.scss';
|
import styles from './StopwatchEditStep.module.scss';
|
||||||
|
|
||||||
const createData = (timer) => {
|
const createData = (stopwatch) => {
|
||||||
if (!timer) {
|
if (!stopwatch) {
|
||||||
return {
|
return {
|
||||||
hours: '0',
|
hours: '0',
|
||||||
minutes: '0',
|
minutes: '0',
|
||||||
@@ -20,7 +26,7 @@ const createData = (timer) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hours, minutes, seconds } = getTimerParts(timer);
|
const { hours, minutes, seconds } = getStopwatchParts(stopwatch);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hours: `${hours}`,
|
hours: `${hours}`,
|
||||||
@@ -29,7 +35,7 @@ const createData = (timer) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const TimerEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) => {
|
const StopwatchEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const [data, handleFieldChange, setData] = useForm(() => createData(defaultValue));
|
const [data, handleFieldChange, setData] = useForm(() => createData(defaultValue));
|
||||||
const [isEditing, toggleEditing] = useToggle();
|
const [isEditing, toggleEditing] = useToggle();
|
||||||
@@ -39,12 +45,12 @@ const TimerEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) =
|
|||||||
const secondsField = useRef(null);
|
const secondsField = useRef(null);
|
||||||
|
|
||||||
const handleStartClick = useCallback(() => {
|
const handleStartClick = useCallback(() => {
|
||||||
onUpdate(startTimer(defaultValue));
|
onUpdate(startStopwatch(defaultValue));
|
||||||
onClose();
|
onClose();
|
||||||
}, [defaultValue, onUpdate, onClose]);
|
}, [defaultValue, onUpdate, onClose]);
|
||||||
|
|
||||||
const handleStopClick = useCallback(() => {
|
const handleStopClick = useCallback(() => {
|
||||||
onUpdate(stopTimer(defaultValue));
|
onUpdate(stopStopwatch(defaultValue));
|
||||||
}, [defaultValue, onUpdate]);
|
}, [defaultValue, onUpdate]);
|
||||||
|
|
||||||
const handleClearClick = useCallback(() => {
|
const handleClearClick = useCallback(() => {
|
||||||
@@ -83,11 +89,11 @@ const TimerEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (defaultValue) {
|
if (defaultValue) {
|
||||||
if (!dequal(parts, getTimerParts(defaultValue))) {
|
if (!dequal(parts, getStopwatchParts(defaultValue))) {
|
||||||
onUpdate(updateTimer(defaultValue, parts));
|
onUpdate(updateStopwatch(defaultValue, parts));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onUpdate(createTimer(parts));
|
onUpdate(createStopwatch(parts));
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
@@ -102,7 +108,7 @@ const TimerEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) =
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Popup.Header onBack={onBack}>
|
<Popup.Header onBack={onBack}>
|
||||||
{t('common.editTimer', {
|
{t('common.editStopwatch', {
|
||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Popup.Header>
|
</Popup.Header>
|
||||||
@@ -171,16 +177,16 @@ const TimerEditStep = React.memo(({ defaultValue, onUpdate, onBack, onClose }) =
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
TimerEditStep.propTypes = {
|
StopwatchEditStep.propTypes = {
|
||||||
defaultValue: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
defaultValue: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
onBack: PropTypes.func,
|
onBack: PropTypes.func,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
TimerEditStep.defaultProps = {
|
StopwatchEditStep.defaultProps = {
|
||||||
defaultValue: undefined,
|
defaultValue: undefined,
|
||||||
onBack: undefined,
|
onBack: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TimerEditStep;
|
export default StopwatchEditStep;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import StopwatchEditStep from './StopwatchEditStep';
|
||||||
|
|
||||||
|
export default StopwatchEditStep;
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Timer from './Timer';
|
|
||||||
|
|
||||||
export default Timer;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import TimerEditStep from './TimerEditStep';
|
|
||||||
|
|
||||||
export default TimerEditStep;
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
|
import omit from 'lodash/omit';
|
||||||
import pickBy from 'lodash/pickBy';
|
import pickBy from 'lodash/pickBy';
|
||||||
import React, { useCallback, useMemo, useRef } from 'react';
|
import React, { useCallback, useMemo, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
@@ -9,7 +10,7 @@ import { useForm } from '../../hooks';
|
|||||||
|
|
||||||
import styles from './UserInformationEdit.module.scss';
|
import styles from './UserInformationEdit.module.scss';
|
||||||
|
|
||||||
const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
const UserInformationEdit = React.memo(({ defaultData, isNameEditable, onUpdate }) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const [data, handleFieldChange] = useForm(() => ({
|
const [data, handleFieldChange] = useForm(() => ({
|
||||||
@@ -32,13 +33,17 @@ const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
|||||||
const nameField = useRef(null);
|
const nameField = useRef(null);
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
|
if (isNameEditable) {
|
||||||
if (!cleanData.name) {
|
if (!cleanData.name) {
|
||||||
nameField.current.select();
|
nameField.current.select();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate(cleanData);
|
onUpdate(cleanData);
|
||||||
}, [onUpdate, cleanData]);
|
} else {
|
||||||
|
onUpdate(omit(cleanData, 'name'));
|
||||||
|
}
|
||||||
|
}, [isNameEditable, onUpdate, cleanData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
@@ -48,6 +53,7 @@ const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
|||||||
ref={nameField}
|
ref={nameField}
|
||||||
name="name"
|
name="name"
|
||||||
value={data.name}
|
value={data.name}
|
||||||
|
disabled={!isNameEditable}
|
||||||
className={styles.field}
|
className={styles.field}
|
||||||
onChange={handleFieldChange}
|
onChange={handleFieldChange}
|
||||||
/>
|
/>
|
||||||
@@ -74,6 +80,7 @@ const UserInformationEdit = React.memo(({ defaultData, onUpdate }) => {
|
|||||||
|
|
||||||
UserInformationEdit.propTypes = {
|
UserInformationEdit.propTypes = {
|
||||||
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
isNameEditable: PropTypes.bool.isRequired,
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { Popup } from '../lib/custom-ui';
|
|||||||
|
|
||||||
import UserInformationEdit from './UserInformationEdit';
|
import UserInformationEdit from './UserInformationEdit';
|
||||||
|
|
||||||
const UserInformationEditStep = React.memo(({ defaultData, onUpdate, onBack, onClose }) => {
|
const UserInformationEditStep = React.memo(
|
||||||
|
({ defaultData, isNameEditable, onUpdate, onBack, onClose }) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const handleUpdate = useCallback(
|
const handleUpdate = useCallback(
|
||||||
@@ -24,14 +25,20 @@ const UserInformationEditStep = React.memo(({ defaultData, onUpdate, onBack, onC
|
|||||||
})}
|
})}
|
||||||
</Popup.Header>
|
</Popup.Header>
|
||||||
<Popup.Content>
|
<Popup.Content>
|
||||||
<UserInformationEdit defaultData={defaultData} onUpdate={handleUpdate} />
|
<UserInformationEdit
|
||||||
|
defaultData={defaultData}
|
||||||
|
isNameEditable={isNameEditable}
|
||||||
|
onUpdate={handleUpdate}
|
||||||
|
/>
|
||||||
</Popup.Content>
|
</Popup.Content>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
UserInformationEditStep.propTypes = {
|
UserInformationEditStep.propTypes = {
|
||||||
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
isNameEditable: PropTypes.bool.isRequired,
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
onBack: PropTypes.func,
|
onBack: PropTypes.func,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ const AccountPane = React.memo(
|
|||||||
phone,
|
phone,
|
||||||
organization,
|
organization,
|
||||||
language,
|
language,
|
||||||
|
isLocked,
|
||||||
|
isUsernameLocked,
|
||||||
isAvatarUpdating,
|
isAvatarUpdating,
|
||||||
usernameUpdateForm,
|
usernameUpdateForm,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
@@ -74,6 +76,7 @@ const AccountPane = React.memo(
|
|||||||
phone,
|
phone,
|
||||||
organization,
|
organization,
|
||||||
}}
|
}}
|
||||||
|
isNameEditable={!isLocked}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
/>
|
/>
|
||||||
<Divider horizontal section>
|
<Divider horizontal section>
|
||||||
@@ -102,6 +105,8 @@ const AccountPane = React.memo(
|
|||||||
value={language || 'auto'}
|
value={language || 'auto'}
|
||||||
onChange={handleLanguageChange}
|
onChange={handleLanguageChange}
|
||||||
/>
|
/>
|
||||||
|
{(!isLocked || !isUsernameLocked) && (
|
||||||
|
<>
|
||||||
<Divider horizontal section>
|
<Divider horizontal section>
|
||||||
<Header as="h4">
|
<Header as="h4">
|
||||||
{t('common.authentication', {
|
{t('common.authentication', {
|
||||||
@@ -109,13 +114,14 @@ const AccountPane = React.memo(
|
|||||||
})}
|
})}
|
||||||
</Header>
|
</Header>
|
||||||
</Divider>
|
</Divider>
|
||||||
|
{!isUsernameLocked && (
|
||||||
<div className={styles.action}>
|
<div className={styles.action}>
|
||||||
<UserUsernameEditPopup
|
<UserUsernameEditPopup
|
||||||
usePasswordConfirmation
|
|
||||||
defaultData={usernameUpdateForm.data}
|
defaultData={usernameUpdateForm.data}
|
||||||
username={username}
|
username={username}
|
||||||
isSubmitting={usernameUpdateForm.isSubmitting}
|
isSubmitting={usernameUpdateForm.isSubmitting}
|
||||||
error={usernameUpdateForm.error}
|
error={usernameUpdateForm.error}
|
||||||
|
usePasswordConfirmation={!isLocked} // FIXME: hack
|
||||||
onUpdate={onUsernameUpdate}
|
onUpdate={onUsernameUpdate}
|
||||||
onMessageDismiss={onUsernameUpdateMessageDismiss}
|
onMessageDismiss={onUsernameUpdateMessageDismiss}
|
||||||
>
|
>
|
||||||
@@ -126,6 +132,9 @@ const AccountPane = React.memo(
|
|||||||
</Button>
|
</Button>
|
||||||
</UserUsernameEditPopup>
|
</UserUsernameEditPopup>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{!isLocked && (
|
||||||
|
<>
|
||||||
<div className={styles.action}>
|
<div className={styles.action}>
|
||||||
<UserEmailEditPopup
|
<UserEmailEditPopup
|
||||||
usePasswordConfirmation
|
usePasswordConfirmation
|
||||||
@@ -159,6 +168,10 @@ const AccountPane = React.memo(
|
|||||||
</Button>
|
</Button>
|
||||||
</UserPasswordEditPopup>
|
</UserPasswordEditPopup>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -172,6 +185,8 @@ AccountPane.propTypes = {
|
|||||||
phone: PropTypes.string,
|
phone: PropTypes.string,
|
||||||
organization: PropTypes.string,
|
organization: PropTypes.string,
|
||||||
language: PropTypes.string,
|
language: PropTypes.string,
|
||||||
|
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,
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ const UserSettingsModal = React.memo(
|
|||||||
organization,
|
organization,
|
||||||
language,
|
language,
|
||||||
subscribeToOwnCards,
|
subscribeToOwnCards,
|
||||||
|
isLocked,
|
||||||
|
isUsernameLocked,
|
||||||
isAvatarUpdating,
|
isAvatarUpdating,
|
||||||
usernameUpdateForm,
|
usernameUpdateForm,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
@@ -48,6 +50,8 @@ const UserSettingsModal = React.memo(
|
|||||||
phone={phone}
|
phone={phone}
|
||||||
organization={organization}
|
organization={organization}
|
||||||
language={language}
|
language={language}
|
||||||
|
isLocked={isLocked}
|
||||||
|
isUsernameLocked={isUsernameLocked}
|
||||||
isAvatarUpdating={isAvatarUpdating}
|
isAvatarUpdating={isAvatarUpdating}
|
||||||
usernameUpdateForm={usernameUpdateForm}
|
usernameUpdateForm={usernameUpdateForm}
|
||||||
emailUpdateForm={emailUpdateForm}
|
emailUpdateForm={emailUpdateForm}
|
||||||
@@ -105,6 +109,8 @@ UserSettingsModal.propTypes = {
|
|||||||
organization: PropTypes.string,
|
organization: PropTypes.string,
|
||||||
language: PropTypes.string,
|
language: PropTypes.string,
|
||||||
subscribeToOwnCards: PropTypes.bool.isRequired,
|
subscribeToOwnCards: 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,
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ const ActionsStep = React.memo(
|
|||||||
return (
|
return (
|
||||||
<UserInformationEditStep
|
<UserInformationEditStep
|
||||||
defaultData={pick(user, ['name', 'phone', 'organization'])}
|
defaultData={pick(user, ['name', 'phone', 'organization'])}
|
||||||
|
isNameEditable={!user.isLocked}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
@@ -135,11 +136,15 @@ const ActionsStep = React.memo(
|
|||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
{!user.isUsernameLocked && (
|
||||||
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
|
<Menu.Item className={styles.menuItem} onClick={handleEditUsernameClick}>
|
||||||
{t('action.editUsername', {
|
{t('action.editUsername', {
|
||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
{!user.isLocked && (
|
||||||
|
<>
|
||||||
<Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}>
|
<Menu.Item className={styles.menuItem} onClick={handleEditEmailClick}>
|
||||||
{t('action.editEmail', {
|
{t('action.editEmail', {
|
||||||
context: 'title',
|
context: 'title',
|
||||||
@@ -150,11 +155,15 @@ const ActionsStep = React.memo(
|
|||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!user.isDeletionLocked && (
|
||||||
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
|
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
|
||||||
{t('action.deleteUser', {
|
{t('action.deleteUser', {
|
||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
</Popup.Content>
|
</Popup.Content>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ const Item = React.memo(
|
|||||||
organization,
|
organization,
|
||||||
phone,
|
phone,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
|
isLocked,
|
||||||
|
isRoleLocked,
|
||||||
|
isUsernameLocked,
|
||||||
|
isDeletionLocked,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
passwordUpdateForm,
|
passwordUpdateForm,
|
||||||
usernameUpdateForm,
|
usernameUpdateForm,
|
||||||
@@ -46,7 +50,7 @@ const Item = React.memo(
|
|||||||
<Table.Cell>{username || '-'}</Table.Cell>
|
<Table.Cell>{username || '-'}</Table.Cell>
|
||||||
<Table.Cell>{email}</Table.Cell>
|
<Table.Cell>{email}</Table.Cell>
|
||||||
<Table.Cell>
|
<Table.Cell>
|
||||||
<Radio toggle checked={isAdmin} onChange={handleIsAdminChange} />
|
<Radio toggle checked={isAdmin} disabled={isRoleLocked} onChange={handleIsAdminChange} />
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
<Table.Cell textAlign="right">
|
<Table.Cell textAlign="right">
|
||||||
<ActionsPopup
|
<ActionsPopup
|
||||||
@@ -57,6 +61,9 @@ const Item = React.memo(
|
|||||||
organization,
|
organization,
|
||||||
phone,
|
phone,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
|
isLocked,
|
||||||
|
isUsernameLocked,
|
||||||
|
isDeletionLocked,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
passwordUpdateForm,
|
passwordUpdateForm,
|
||||||
usernameUpdateForm,
|
usernameUpdateForm,
|
||||||
@@ -88,6 +95,10 @@ Item.propTypes = {
|
|||||||
organization: PropTypes.string,
|
organization: PropTypes.string,
|
||||||
phone: PropTypes.string,
|
phone: PropTypes.string,
|
||||||
isAdmin: PropTypes.bool.isRequired,
|
isAdmin: PropTypes.bool.isRequired,
|
||||||
|
isLocked: PropTypes.bool.isRequired,
|
||||||
|
isRoleLocked: PropTypes.bool.isRequired,
|
||||||
|
isUsernameLocked: PropTypes.bool.isRequired,
|
||||||
|
isDeletionLocked: PropTypes.bool.isRequired,
|
||||||
/* eslint-disable react/forbid-prop-types */
|
/* eslint-disable react/forbid-prop-types */
|
||||||
emailUpdateForm: PropTypes.object.isRequired,
|
emailUpdateForm: PropTypes.object.isRequired,
|
||||||
passwordUpdateForm: PropTypes.object.isRequired,
|
passwordUpdateForm: PropTypes.object.isRequired,
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ const UsersModal = React.memo(
|
|||||||
organization={item.organization}
|
organization={item.organization}
|
||||||
phone={item.phone}
|
phone={item.phone}
|
||||||
isAdmin={item.isAdmin}
|
isAdmin={item.isAdmin}
|
||||||
|
isLocked={item.isLocked}
|
||||||
|
isRoleLocked={item.isRoleLocked}
|
||||||
|
isUsernameLocked={item.isUsernameLocked}
|
||||||
|
isDeletionLocked={item.isDeletionLocked}
|
||||||
emailUpdateForm={item.emailUpdateForm}
|
emailUpdateForm={item.emailUpdateForm}
|
||||||
passwordUpdateForm={item.passwordUpdateForm}
|
passwordUpdateForm={item.passwordUpdateForm}
|
||||||
usernameUpdateForm={item.usernameUpdateForm}
|
usernameUpdateForm={item.usernameUpdateForm}
|
||||||
|
|||||||
@@ -12,14 +12,19 @@ export default {
|
|||||||
|
|
||||||
/* Login */
|
/* Login */
|
||||||
|
|
||||||
|
LOGIN_INITIALIZE: 'LOGIN_INITIALIZE',
|
||||||
AUTHENTICATE: 'AUTHENTICATE',
|
AUTHENTICATE: 'AUTHENTICATE',
|
||||||
AUTHENTICATE__SUCCESS: 'AUTHENTICATE__SUCCESS',
|
AUTHENTICATE__SUCCESS: 'AUTHENTICATE__SUCCESS',
|
||||||
AUTHENTICATE__FAILURE: 'AUTHENTICATE__FAILURE',
|
AUTHENTICATE__FAILURE: 'AUTHENTICATE__FAILURE',
|
||||||
|
USING_OIDC_AUTHENTICATE: 'USING_OIDC_AUTHENTICATE',
|
||||||
|
USING_OIDC_AUTHENTICATE__SUCCESS: 'USING_OIDC_AUTHENTICATE__SUCCESS',
|
||||||
|
USING_OIDC_AUTHENTICATE__FAILURE: 'USING_OIDC_AUTHENTICATE__FAILURE',
|
||||||
AUTHENTICATE_ERROR_CLEAR: 'AUTHENTICATE_ERROR_CLEAR',
|
AUTHENTICATE_ERROR_CLEAR: 'AUTHENTICATE_ERROR_CLEAR',
|
||||||
|
|
||||||
/* Core */
|
/* Core */
|
||||||
|
|
||||||
CORE_INITIALIZE: 'CORE_INITIALIZE',
|
CORE_INITIALIZE: 'CORE_INITIALIZE',
|
||||||
|
CORE_INITIALIZE__CONFIG_FETCH: 'CORE_INITIALIZE__CONFIG_FETCH',
|
||||||
LOGOUT: 'LOGOUT',
|
LOGOUT: 'LOGOUT',
|
||||||
LOGOUT__ACCESS_TOKEN_INVALIDATE: 'LOGOUT__ACCESS_TOKEN_INVALIDATE',
|
LOGOUT__ACCESS_TOKEN_INVALIDATE: 'LOGOUT__ACCESS_TOKEN_INVALIDATE',
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ export default {
|
|||||||
/* Login */
|
/* Login */
|
||||||
|
|
||||||
AUTHENTICATE: `${PREFIX}/AUTHENTICATE`,
|
AUTHENTICATE: `${PREFIX}/AUTHENTICATE`,
|
||||||
|
USING_OIDC_AUTHENTICATE: `${PREFIX}/USING_OIDC_AUTHENTICATE`,
|
||||||
AUTHENTICATE_ERROR_CLEAR: `${PREFIX}/AUTHENTICATE_ERROR_CLEAR`,
|
AUTHENTICATE_ERROR_CLEAR: `${PREFIX}/AUTHENTICATE_ERROR_CLEAR`,
|
||||||
|
|
||||||
/* Core */
|
/* Core */
|
||||||
|
|
||||||
CORE_INITIALIZE: `${PREFIX}/CORE_INITIALIZE`,
|
|
||||||
LOGOUT: `${PREFIX}/LOGOUT`,
|
LOGOUT: `${PREFIX}/LOGOUT`,
|
||||||
|
|
||||||
/* Modals */
|
/* Modals */
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Config from './Config';
|
|||||||
|
|
||||||
const ROOT = `${Config.BASE_PATH}/`;
|
const ROOT = `${Config.BASE_PATH}/`;
|
||||||
const LOGIN = `${Config.BASE_PATH}/login`;
|
const LOGIN = `${Config.BASE_PATH}/login`;
|
||||||
|
const OIDC_CALLBACK = `${Config.BASE_PATH}/oidc-callback`;
|
||||||
const PROJECTS = `${Config.BASE_PATH}/projects/:id`;
|
const PROJECTS = `${Config.BASE_PATH}/projects/:id`;
|
||||||
const BOARDS = `${Config.BASE_PATH}/boards/:id`;
|
const BOARDS = `${Config.BASE_PATH}/boards/:id`;
|
||||||
const CARDS = `${Config.BASE_PATH}/cards/:id`;
|
const CARDS = `${Config.BASE_PATH}/cards/:id`;
|
||||||
@@ -9,6 +10,7 @@ const CARDS = `${Config.BASE_PATH}/cards/:id`;
|
|||||||
export default {
|
export default {
|
||||||
ROOT,
|
ROOT,
|
||||||
LOGIN,
|
LOGIN,
|
||||||
|
OIDC_CALLBACK,
|
||||||
PROJECTS,
|
PROJECTS,
|
||||||
BOARDS,
|
BOARDS,
|
||||||
CARDS,
|
CARDS,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const makeMapStateToProps = () => {
|
|||||||
const allLabels = selectors.selectLabelsForCurrentBoard(state);
|
const allLabels = selectors.selectLabelsForCurrentBoard(state);
|
||||||
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
const currentUserMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
|
||||||
|
|
||||||
const { name, dueDate, timer, coverUrl, boardId, listId, isPersisted } = selectCardById(
|
const { name, dueDate, stopwatch, coverUrl, boardId, listId, isPersisted } = selectCardById(
|
||||||
state,
|
state,
|
||||||
id,
|
id,
|
||||||
);
|
);
|
||||||
@@ -38,7 +38,7 @@ const makeMapStateToProps = () => {
|
|||||||
index,
|
index,
|
||||||
name,
|
name,
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
coverUrl,
|
coverUrl,
|
||||||
boardId,
|
boardId,
|
||||||
listId,
|
listId,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const mapStateToProps = (state) => {
|
|||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
isActivitiesFetching,
|
isActivitiesFetching,
|
||||||
isAllActivitiesFetched,
|
isAllActivitiesFetched,
|
||||||
@@ -49,7 +49,7 @@ const mapStateToProps = (state) => {
|
|||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
dueDate,
|
dueDate,
|
||||||
timer,
|
stopwatch,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
isActivitiesFetching,
|
isActivitiesFetching,
|
||||||
isAllActivitiesFetched,
|
isAllActivitiesFetched,
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ import selectors from '../selectors';
|
|||||||
import Core from '../components/Core';
|
import Core from '../components/Core';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
const isCoreInitializing = selectors.selectIsCoreInitializing(state);
|
const isInitializing = selectors.selectIsInitializing(state);
|
||||||
const isSocketDisconnected = selectors.selectIsSocketDisconnected(state);
|
const isSocketDisconnected = selectors.selectIsSocketDisconnected(state);
|
||||||
const currentModal = selectors.selectCurrentModal(state);
|
const currentModal = selectors.selectCurrentModal(state);
|
||||||
const currentProject = selectors.selectCurrentProject(state);
|
const currentProject = selectors.selectCurrentProject(state);
|
||||||
const currentBoard = selectors.selectCurrentBoard(state);
|
const currentBoard = selectors.selectCurrentBoard(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isInitializing,
|
||||||
isSocketDisconnected,
|
isSocketDisconnected,
|
||||||
currentModal,
|
currentModal,
|
||||||
currentProject,
|
currentProject,
|
||||||
currentBoard,
|
currentBoard,
|
||||||
isInitializing: isCoreInitializing,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,33 @@
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import selectors from '../selectors';
|
||||||
import entryActions from '../entry-actions';
|
import entryActions from '../entry-actions';
|
||||||
import Login from '../components/Login';
|
import Login from '../components/Login';
|
||||||
|
|
||||||
const mapStateToProps = ({
|
const mapStateToProps = (state) => {
|
||||||
|
const oidcConfig = selectors.selectOidcConfig(state);
|
||||||
|
|
||||||
|
const {
|
||||||
ui: {
|
ui: {
|
||||||
authenticateForm: { data: defaultData, isSubmitting, error },
|
authenticateForm: { data: defaultData, isSubmitting, isSubmittingUsingOidc, error },
|
||||||
},
|
},
|
||||||
}) => ({
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
defaultData,
|
defaultData,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
|
isSubmittingUsingOidc,
|
||||||
error,
|
error,
|
||||||
});
|
withOidc: !!oidcConfig,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) =>
|
const mapDispatchToProps = (dispatch) =>
|
||||||
bindActionCreators(
|
bindActionCreators(
|
||||||
{
|
{
|
||||||
onAuthenticate: entryActions.authenticate,
|
onAuthenticate: entryActions.authenticate,
|
||||||
|
onAuthenticateUsingOidc: entryActions.authenticateUsingOidc,
|
||||||
onMessageDismiss: entryActions.clearAuthenticateError,
|
onMessageDismiss: entryActions.clearAuthenticateError,
|
||||||
},
|
},
|
||||||
dispatch,
|
dispatch,
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import selectors from '../selectors';
|
||||||
|
import LoginWrapper from '../components/LoginWrapper';
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
const isInitializing = selectors.selectIsInitializing(state);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isInitializing,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(LoginWrapper);
|
||||||
@@ -15,6 +15,8 @@ const mapStateToProps = (state) => {
|
|||||||
organization,
|
organization,
|
||||||
language,
|
language,
|
||||||
subscribeToOwnCards,
|
subscribeToOwnCards,
|
||||||
|
isLocked,
|
||||||
|
isUsernameLocked,
|
||||||
isAvatarUpdating,
|
isAvatarUpdating,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
passwordUpdateForm,
|
passwordUpdateForm,
|
||||||
@@ -30,6 +32,8 @@ const mapStateToProps = (state) => {
|
|||||||
organization,
|
organization,
|
||||||
language,
|
language,
|
||||||
subscribeToOwnCards,
|
subscribeToOwnCards,
|
||||||
|
isLocked,
|
||||||
|
isUsernameLocked,
|
||||||
isAvatarUpdating,
|
isAvatarUpdating,
|
||||||
emailUpdateForm,
|
emailUpdateForm,
|
||||||
passwordUpdateForm,
|
passwordUpdateForm,
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
import EntryActionTypes from '../constants/EntryActionTypes';
|
import EntryActionTypes from '../constants/EntryActionTypes';
|
||||||
|
|
||||||
const initializeCore = () => ({
|
|
||||||
type: EntryActionTypes.CORE_INITIALIZE,
|
|
||||||
payload: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
const logout = () => ({
|
const logout = () => ({
|
||||||
type: EntryActionTypes.LOGOUT,
|
type: EntryActionTypes.LOGOUT,
|
||||||
payload: {},
|
payload: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
initializeCore,
|
|
||||||
logout,
|
logout,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ const authenticate = (data) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const authenticateUsingOidc = () => ({
|
||||||
|
type: EntryActionTypes.USING_OIDC_AUTHENTICATE,
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
|
||||||
const clearAuthenticateError = () => ({
|
const clearAuthenticateError = () => ({
|
||||||
type: EntryActionTypes.AUTHENTICATE_ERROR_CLEAR,
|
type: EntryActionTypes.AUTHENTICATE_ERROR_CLEAR,
|
||||||
payload: {},
|
payload: {},
|
||||||
@@ -14,5 +19,6 @@ const clearAuthenticateError = () => ({
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
authenticate,
|
authenticate,
|
||||||
|
authenticateUsingOidc,
|
||||||
clearAuthenticateError,
|
clearAuthenticateError,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ export default {
|
|||||||
dateTime: '$t(format:date) $t(format:time)',
|
dateTime: '$t(format:date) $t(format:time)',
|
||||||
longDate: 'd MMM',
|
longDate: 'd MMM',
|
||||||
longDateTime: "d MMMM 'v' p",
|
longDateTime: "d MMMM 'v' p",
|
||||||
|
fullDate: 'd MMM, y',
|
||||||
|
fullDateTime: "d MMMM, y 'v' p",
|
||||||
},
|
},
|
||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
|
aboutPlanka: 'O aplikaci Planka',
|
||||||
account: 'Účet',
|
account: 'Účet',
|
||||||
actions: 'Akce',
|
actions: 'Akce',
|
||||||
addAttachment_title: 'Přidat přílohu',
|
addAttachment_title: 'Přidat přílohu',
|
||||||
@@ -23,7 +26,7 @@ export default {
|
|||||||
administrator: 'Administrátor',
|
administrator: 'Administrátor',
|
||||||
all: 'Vše',
|
all: 'Vše',
|
||||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||||
'Všechny změny budou automaticky uloženy<br />po obnově spojení',
|
'Všechny změny budou automaticky uloženy<br />po obnově spojení.',
|
||||||
areYouSureYouWantToDeleteThisAttachment: 'Opravdu chcete smazat tuto přílohu?',
|
areYouSureYouWantToDeleteThisAttachment: 'Opravdu chcete smazat tuto přílohu?',
|
||||||
areYouSureYouWantToDeleteThisBoard: 'Opravdu chcete smazat tuto tabuli?',
|
areYouSureYouWantToDeleteThisBoard: 'Opravdu chcete smazat tuto tabuli?',
|
||||||
areYouSureYouWantToDeleteThisCard: 'Opravdu chcete smazat tuto kartu?',
|
areYouSureYouWantToDeleteThisCard: 'Opravdu chcete smazat tuto kartu?',
|
||||||
@@ -45,13 +48,16 @@ export default {
|
|||||||
background: 'Pozadí',
|
background: 'Pozadí',
|
||||||
board: 'Tabule',
|
board: 'Tabule',
|
||||||
boardNotFound_title: 'Tabule nenalezena',
|
boardNotFound_title: 'Tabule nenalezena',
|
||||||
|
canComment: 'Může komentovat',
|
||||||
|
canEditContentOfBoard: 'Může upravovat obsah tabule.',
|
||||||
|
canOnlyViewBoard: 'Může zobrazit pouze tabuli.',
|
||||||
cardActions_title: 'Akce na kartě',
|
cardActions_title: 'Akce na kartě',
|
||||||
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',
|
||||||
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í.',
|
||||||
createProject_title: 'Vytvořit projekt',
|
createProject_title: 'Vytvořit projekt',
|
||||||
createTextFile_title: 'Vytvořit textový soubor',
|
createTextFile_title: 'Vytvořit textový soubor',
|
||||||
currentPassword: 'Aktuální heslo',
|
currentPassword: 'Aktuální heslo',
|
||||||
@@ -68,15 +74,19 @@ export default {
|
|||||||
deleteTask_title: 'Smazat úkol',
|
deleteTask_title: 'Smazat úkol',
|
||||||
deleteUser_title: 'Smazat uživatele',
|
deleteUser_title: 'Smazat uživatele',
|
||||||
description: 'Popis',
|
description: 'Popis',
|
||||||
|
detectAutomatically: 'Automatická detekce',
|
||||||
dropFileToUpload: 'Přetáhnutím nahraj soubor',
|
dropFileToUpload: 'Přetáhnutím nahraj soubor',
|
||||||
|
editor: 'Editor',
|
||||||
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 do',
|
||||||
editEmail_title: 'Upravit e-mail',
|
editEmail_title: 'Upravit e-mail',
|
||||||
|
editInformation_title: 'Upravit informace',
|
||||||
editLabel_title: 'Upravit štítek',
|
editLabel_title: 'Upravit štítek',
|
||||||
editPassword_title: 'Upravit heslo',
|
editPassword_title: 'Upravit heslo',
|
||||||
editTimer_title: 'Upravit časovač',
|
editPermissions_title: 'Upravit oprávnění',
|
||||||
|
editStopwatch_title: 'Upravit časovač',
|
||||||
editUsername_title: 'Upravit uživatelské jméno',
|
editUsername_title: 'Upravit uživatelské jméno',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail se již používá',
|
emailAlreadyInUse: 'E-mail se již používá',
|
||||||
@@ -89,10 +99,13 @@ export default {
|
|||||||
filterByLabels_title: 'Filtruj podle štítku',
|
filterByLabels_title: 'Filtruj podle štítku',
|
||||||
filterByMembers_title: 'Filtruj podle člena',
|
filterByMembers_title: 'Filtruj podle člena',
|
||||||
fromComputer_title: 'Z počítače',
|
fromComputer_title: 'Z počítače',
|
||||||
|
fromTrello: 'Z Trella',
|
||||||
general: 'Obecné',
|
general: 'Obecné',
|
||||||
hours: 'Hodiny',
|
hours: 'Hodiny',
|
||||||
|
importBoard_title: 'Importovat tabuli',
|
||||||
invalidCurrentPassword: 'Neplatné aktuální heslo',
|
invalidCurrentPassword: 'Neplatné aktuální heslo',
|
||||||
labels: 'Štítky',
|
labels: 'Štítky',
|
||||||
|
language: 'Jazyk',
|
||||||
leaveBoard_title: 'Opustit tabuli',
|
leaveBoard_title: 'Opustit tabuli',
|
||||||
leaveProject_title: 'Opustit projekt',
|
leaveProject_title: 'Opustit projekt',
|
||||||
list: 'Seznam',
|
list: 'Seznam',
|
||||||
@@ -117,21 +130,26 @@ export default {
|
|||||||
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: dejte Ctrl-V (Cmd-V na Mac) pro vložení přílohy 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',
|
||||||
removeMember_title: 'Odstranit člena',
|
removeMember_title: 'Odstranit člena',
|
||||||
|
searchLabels: 'Hledat štítky...',
|
||||||
|
searchMembers: 'Hledat členy...',
|
||||||
|
searchUsers: 'Hledat uživatele...',
|
||||||
seconds: 'Vteřin',
|
seconds: 'Vteřin',
|
||||||
selectBoard: 'Vybrat tabuli',
|
selectBoard: 'Vybrat tabuli',
|
||||||
selectList: 'Vybrat seznam',
|
selectList: 'Vybrat seznam',
|
||||||
|
selectPermissions_title: 'Vybrat oprávnění',
|
||||||
selectProject: 'Vybrat projekt',
|
selectProject: 'Vybrat projekt',
|
||||||
settings: 'Nastavení',
|
settings: 'Nastavení',
|
||||||
|
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',
|
||||||
tasks: 'Úkoly',
|
tasks: 'Úkoly',
|
||||||
|
thereIsNoPreviewAvailableForThisAttachment: 'Pro tuto přílohu není k dispozici žádný náhled.',
|
||||||
time: 'Čas',
|
time: 'Čas',
|
||||||
timer: 'Časovač',
|
|
||||||
title: 'Titulek',
|
title: 'Titulek',
|
||||||
userActions_title: 'Akce na uživateli',
|
userActions_title: 'Akce na uživateli',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> přidal kartu do {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> přidal kartu do {{list}}</1>',
|
||||||
@@ -142,6 +160,8 @@ export default {
|
|||||||
username: 'Uživatelské jméno',
|
username: 'Uživatelské jméno',
|
||||||
usernameAlreadyInUse: 'Uživatelské jméno se již používá',
|
usernameAlreadyInUse: 'Uživatelské jméno se již používá',
|
||||||
users: 'Uživatelé',
|
users: 'Uživatelé',
|
||||||
|
version: 'Verze',
|
||||||
|
viewer: 'Prohlížeč',
|
||||||
writeComment: 'Napsat komentář...',
|
writeComment: 'Napsat komentář...',
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -153,6 +173,7 @@ export default {
|
|||||||
addCard_title: 'Přidat kartu',
|
addCard_title: 'Přidat kartu',
|
||||||
addComment: 'Přidat komentář',
|
addComment: 'Přidat komentář',
|
||||||
addList: 'Přidat seznam',
|
addList: 'Přidat seznam',
|
||||||
|
addMember: 'Přidat člena',
|
||||||
addMoreDetailedDescription: 'Přidat další detailní popis',
|
addMoreDetailedDescription: 'Přidat další detailní popis',
|
||||||
addTask: 'Přidat úkol',
|
addTask: 'Přidat úkol',
|
||||||
addToCard: 'Přidat na kartu',
|
addToCard: 'Přidat na kartu',
|
||||||
@@ -182,10 +203,14 @@ export default {
|
|||||||
editDueDate_title: 'Upravit termín do',
|
editDueDate_title: 'Upravit termín do',
|
||||||
editDescription_title: 'Upravit popis',
|
editDescription_title: 'Upravit popis',
|
||||||
editEmail_title: 'Upravit e-mail',
|
editEmail_title: 'Upravit e-mail',
|
||||||
|
editInformation_title: 'Upravit informace',
|
||||||
editPassword_title: 'Upravit heslo',
|
editPassword_title: 'Upravit heslo',
|
||||||
editTimer_title: 'Upravit časovač',
|
editPermissions: 'Upravit oprávnění',
|
||||||
|
editStopwatch_title: 'Upravit časovač',
|
||||||
editTitle_title: 'Upravit titulek',
|
editTitle_title: 'Upravit titulek',
|
||||||
editUsername_title: 'Upravit uživatelské jméno',
|
editUsername_title: 'Upravit uživatelské jméno',
|
||||||
|
hideDetails: 'Skrýt detaily',
|
||||||
|
import: 'Import',
|
||||||
leaveBoard: 'Opustit tabuli',
|
leaveBoard: 'Opustit tabuli',
|
||||||
leaveProject: 'Opustit projekt',
|
leaveProject: 'Opustit projekt',
|
||||||
logOut_title: 'Odhlásit se',
|
logOut_title: 'Odhlásit se',
|
||||||
@@ -202,6 +227,7 @@ export default {
|
|||||||
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',
|
showFewerAttachments: 'Zobrazit méně příloh',
|
||||||
|
showDetails: 'Zobrazit detaily',
|
||||||
start: 'Start',
|
start: 'Start',
|
||||||
stop: 'Stop',
|
stop: 'Stop',
|
||||||
subscribe: 'Odebírat',
|
subscribe: 'Odebírat',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export default {
|
|||||||
dateTime: '$t(format:date) $t(format:time)',
|
dateTime: '$t(format:date) $t(format:time)',
|
||||||
longDate: 'MMM d',
|
longDate: 'MMM d',
|
||||||
longDateTime: "MMMM d 'at' p",
|
longDateTime: "MMMM d 'at' p",
|
||||||
|
fullDate: 'MMM d, y',
|
||||||
|
fullDateTime: "MMMM d, y 'a' p",
|
||||||
},
|
},
|
||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
@@ -23,7 +25,7 @@ export default {
|
|||||||
administrator: 'Administrator',
|
administrator: 'Administrator',
|
||||||
all: 'Alle',
|
all: 'Alle',
|
||||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||||
'Alle ændringer vil automatisk blive gemt<br />ved genforbindelse',
|
'Alle ændringer vil automatisk blive gemt<br />ved genforbindelse.',
|
||||||
areYouSureYouWantToDeleteThisAttachment: 'Vil du slette den vedhæftede fil?',
|
areYouSureYouWantToDeleteThisAttachment: 'Vil du slette den vedhæftede fil?',
|
||||||
areYouSureYouWantToDeleteThisBoard: 'Vil du slette dette bræt?',
|
areYouSureYouWantToDeleteThisBoard: 'Vil du slette dette bræt?',
|
||||||
areYouSureYouWantToDeleteThisCard: 'Vil du slette dette kort?',
|
areYouSureYouWantToDeleteThisCard: 'Vil du slette dette kort?',
|
||||||
@@ -46,11 +48,11 @@ export default {
|
|||||||
boardNotFound_title: 'Bræt ikke fundet',
|
boardNotFound_title: 'Bræt ikke fundet',
|
||||||
cardActions_title: 'Korthandlinger',
|
cardActions_title: 'Korthandlinger',
|
||||||
cardNotFound_title: 'Kort ikke fundet',
|
cardNotFound_title: 'Kort ikke fundet',
|
||||||
cardOrActionAreDeleted: 'Kort eller handling er slettet',
|
cardOrActionAreDeleted: 'Kort eller handling er slettet.',
|
||||||
color: 'Farve',
|
color: 'Farve',
|
||||||
createBoard_title: 'Opret bræt',
|
createBoard_title: 'Opret bræt',
|
||||||
createLabel_title: 'Opret mærkat',
|
createLabel_title: 'Opret mærkat',
|
||||||
createNewOneOrSelectExistingOne: 'Lav en ny eller marker<br />en eksisterende',
|
createNewOneOrSelectExistingOne: 'Lav en ny eller marker<br />en eksisterende.',
|
||||||
createProject_title: 'Opret projekt',
|
createProject_title: 'Opret projekt',
|
||||||
createTextFile_title: 'Opret tekstfil',
|
createTextFile_title: 'Opret tekstfil',
|
||||||
currentPassword: 'Nuværende løsen',
|
currentPassword: 'Nuværende løsen',
|
||||||
@@ -75,7 +77,7 @@ export default {
|
|||||||
editEmail_title: 'Ændre E-mail',
|
editEmail_title: 'Ændre E-mail',
|
||||||
editLabel_title: 'ændre mærkat',
|
editLabel_title: 'ændre mærkat',
|
||||||
editPassword_title: 'Ændre løsen',
|
editPassword_title: 'Ændre løsen',
|
||||||
editTimer_title: 'Ændre timer',
|
editStopwatch_title: 'Ændre timer',
|
||||||
editUsername_title: 'Ændre brugernavn',
|
editUsername_title: 'Ændre brugernavn',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail allerede i brug',
|
emailAlreadyInUse: 'E-mail allerede i brug',
|
||||||
@@ -109,14 +111,14 @@ export default {
|
|||||||
noLists: 'Ingen lister',
|
noLists: 'Ingen lister',
|
||||||
noProjects: 'Ingen projekter',
|
noProjects: 'Ingen projekter',
|
||||||
notifications: 'Notifikationer',
|
notifications: 'Notifikationer',
|
||||||
noUnreadNotifications: 'Ingen ulæste notifikationer',
|
noUnreadNotifications: 'Ingen ulæste notifikationer.',
|
||||||
openBoard_title: 'Åben bræt',
|
openBoard_title: 'Åben bræt',
|
||||||
optional_inline: 'valgfri',
|
optional_inline: 'valgfri',
|
||||||
organization: 'Organisation',
|
organization: 'Organisation',
|
||||||
phone: 'Telefon',
|
phone: 'Telefon',
|
||||||
preferences: 'Præferencer',
|
preferences: 'Præferencer',
|
||||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||||
'Tip: Tryk Ctrl-V (Cmd-V på Mac) for at vedhæfte direkte fra udklipsholder',
|
'Tip: Tryk Ctrl-V (Cmd-V på Mac) for at vedhæfte direkte fra udklipsholder.',
|
||||||
project: 'Projekt',
|
project: 'Projekt',
|
||||||
projectNotFound_title: 'Projekt ej fundet',
|
projectNotFound_title: 'Projekt ej fundet',
|
||||||
removeManager_title: 'Fjern projektleder',
|
removeManager_title: 'Fjern projektleder',
|
||||||
@@ -126,11 +128,11 @@ export default {
|
|||||||
selectList: 'Vælg liste',
|
selectList: 'Vælg liste',
|
||||||
selectProject: 'Vælg projekt',
|
selectProject: 'Vælg projekt',
|
||||||
settings: 'Indstillinger',
|
settings: 'Indstillinger',
|
||||||
|
stopwatch: 'Tidsmåler',
|
||||||
subscribeToMyOwnCardsByDefault: 'Abonnér på egne kort som standard',
|
subscribeToMyOwnCardsByDefault: 'Abonnér på egne kort som standard',
|
||||||
taskActions_title: 'Opgave handlinger',
|
taskActions_title: 'Opgave handlinger',
|
||||||
tasks: 'Opgaver',
|
tasks: 'Opgaver',
|
||||||
time: 'Tid',
|
time: 'Tid',
|
||||||
timer: 'Tidsmåler',
|
|
||||||
title: 'Overskrift',
|
title: 'Overskrift',
|
||||||
userActions_title: 'Brugerhandlinger',
|
userActions_title: 'Brugerhandlinger',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> tilføjede kortet til {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> tilføjede kortet til {{list}}</1>',
|
||||||
@@ -183,7 +185,7 @@ export default {
|
|||||||
editDescription_title: 'Ændre beskrivelse',
|
editDescription_title: 'Ændre beskrivelse',
|
||||||
editEmail_title: 'Ændre e-mail',
|
editEmail_title: 'Ændre e-mail',
|
||||||
editPassword_title: 'Ændre Løsen',
|
editPassword_title: 'Ændre Løsen',
|
||||||
editTimer_title: 'Ændre tidstager',
|
editStopwatch_title: 'Ændre tidstager',
|
||||||
editTitle_title: 'Ændre overskrift',
|
editTitle_title: 'Ændre overskrift',
|
||||||
editUsername_title: 'Ændre brugernavn',
|
editUsername_title: 'Ændre brugernavn',
|
||||||
leaveBoard: 'Forlad bræt',
|
leaveBoard: 'Forlad bræt',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export default {
|
|||||||
dateTime: '$t(format:date) $t(format:time)',
|
dateTime: '$t(format:date) $t(format:time)',
|
||||||
longDate: 'd. MMM',
|
longDate: 'd. MMM',
|
||||||
longDateTime: "d. MMMM 'um' p",
|
longDateTime: "d. MMMM 'um' p",
|
||||||
|
fullDate: 'd. MMM. y',
|
||||||
|
fullDateTime: "d. MMMM. y 'um' p",
|
||||||
},
|
},
|
||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
@@ -51,7 +53,7 @@ export default {
|
|||||||
boardNotFound_title: 'Board nicht gefunden',
|
boardNotFound_title: 'Board nicht gefunden',
|
||||||
cardActions_title: 'Kartenaktionen',
|
cardActions_title: 'Kartenaktionen',
|
||||||
cardNotFound_title: 'Karte nicht gefunden',
|
cardNotFound_title: 'Karte nicht gefunden',
|
||||||
cardOrActionAreDeleted: 'Karte oder Aktion wurden gelöscht',
|
cardOrActionAreDeleted: 'Karte oder Aktion wurden gelöscht.',
|
||||||
color: 'Farbe',
|
color: 'Farbe',
|
||||||
createBoard_title: 'Board erstellen',
|
createBoard_title: 'Board erstellen',
|
||||||
createLabel_title: 'Label erstellen',
|
createLabel_title: 'Label erstellen',
|
||||||
@@ -82,7 +84,7 @@ export default {
|
|||||||
editEmail_title: 'E-Mail-Adresse bearbeiten',
|
editEmail_title: 'E-Mail-Adresse bearbeiten',
|
||||||
editLabel_title: 'Label bearbeiten',
|
editLabel_title: 'Label bearbeiten',
|
||||||
editPassword_title: 'Passwort ändern',
|
editPassword_title: 'Passwort ändern',
|
||||||
editTimer_title: 'Timer bearbeiten',
|
editStopwatch_title: 'Stoppuhr bearbeiten',
|
||||||
editUsername_title: 'Benutzername ändern',
|
editUsername_title: 'Benutzername ändern',
|
||||||
email: 'E-Mail-Adresse',
|
email: 'E-Mail-Adresse',
|
||||||
emailAlreadyInUse: 'E-Mail-Adresse bereits in Verwendung',
|
emailAlreadyInUse: 'E-Mail-Adresse bereits in Verwendung',
|
||||||
@@ -117,14 +119,14 @@ export default {
|
|||||||
noLists: 'Keine Listen',
|
noLists: 'Keine Listen',
|
||||||
noProjects: 'Keine Projekte',
|
noProjects: 'Keine Projekte',
|
||||||
notifications: 'Benachrichtigungen',
|
notifications: 'Benachrichtigungen',
|
||||||
noUnreadNotifications: 'Keine ungelesenen Benachrichtigungen',
|
noUnreadNotifications: 'Keine ungelesenen Benachrichtigungen.',
|
||||||
openBoard_title: 'Board öffnen',
|
openBoard_title: 'Board öffnen',
|
||||||
optional_inline: 'Optional',
|
optional_inline: 'Optional',
|
||||||
organization: 'Organisation',
|
organization: 'Organisation',
|
||||||
phone: 'Telefon',
|
phone: 'Telefon',
|
||||||
preferences: 'Voreinstellungen',
|
preferences: 'Voreinstellungen',
|
||||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||||
'Tipp: Drücken Sie STRG-V (Cmd-V auf Mac), um einen Anhang aus der Zwischenablage hinzuzufügen',
|
'Tipp: Drücken Sie STRG-V (Cmd-V auf Mac), um einen Anhang aus der Zwischenablage hinzuzufügen.',
|
||||||
project: 'Projekt',
|
project: 'Projekt',
|
||||||
projectNotFound_title: 'Projekt nicht gefunden',
|
projectNotFound_title: 'Projekt nicht gefunden',
|
||||||
removeManager_title: 'Manager entfernen',
|
removeManager_title: 'Manager entfernen',
|
||||||
@@ -134,12 +136,12 @@ export default {
|
|||||||
selectList: 'Liste auswählen',
|
selectList: 'Liste auswählen',
|
||||||
selectProject: 'Projekt auswählen',
|
selectProject: 'Projekt auswählen',
|
||||||
settings: 'Einstellungen',
|
settings: 'Einstellungen',
|
||||||
|
stopwatch: 'Stoppuhr',
|
||||||
subscribeToMyOwnCardsByDefault: 'Standardmäßig meine eigenen Karten abonnieren',
|
subscribeToMyOwnCardsByDefault: 'Standardmäßig meine eigenen Karten abonnieren',
|
||||||
taskActions_title: 'Aufgabenaktionen',
|
taskActions_title: 'Aufgabenaktionen',
|
||||||
tasks: 'Aufgaben',
|
tasks: 'Aufgaben',
|
||||||
thereIsNoPreviewAvailableForThisAttachment: 'Für diesen Anhang ist keine Vorschau verfügbar',
|
thereIsNoPreviewAvailableForThisAttachment: 'Für diesen Anhang ist keine Vorschau verfügbar',
|
||||||
time: 'Zeit',
|
time: 'Zeit',
|
||||||
timer: 'Timer',
|
|
||||||
title: 'Titel',
|
title: 'Titel',
|
||||||
userActions_title: 'Benutzeraktionen',
|
userActions_title: 'Benutzeraktionen',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> hat diese Karte hinzugefügt zu {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> hat diese Karte hinzugefügt zu {{list}}</1>',
|
||||||
@@ -193,7 +195,7 @@ export default {
|
|||||||
editDescription_title: 'Beschreibung ändern',
|
editDescription_title: 'Beschreibung ändern',
|
||||||
editEmail_title: 'E-Mail-Adresse bearbeiten',
|
editEmail_title: 'E-Mail-Adresse bearbeiten',
|
||||||
editPassword_title: 'Passwort ändern',
|
editPassword_title: 'Passwort ändern',
|
||||||
editTimer_title: 'Timer bearbeiten',
|
editStopwatch_title: 'Stoppuhr bearbeiten',
|
||||||
editTitle_title: 'Titel bearbeiten',
|
editTitle_title: 'Titel bearbeiten',
|
||||||
editUsername_title: 'Benutzername ändern',
|
editUsername_title: 'Benutzername ändern',
|
||||||
hideDetails: 'Details ausblenden',
|
hideDetails: 'Details ausblenden',
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export default {
|
|||||||
dateTime: '$t(format:date) $t(format:time)',
|
dateTime: '$t(format:date) $t(format:time)',
|
||||||
longDate: 'MMM d',
|
longDate: 'MMM d',
|
||||||
longDateTime: "MMMM d 'at' p",
|
longDateTime: "MMMM d 'at' p",
|
||||||
|
fullDate: 'MMM d, y',
|
||||||
|
fullDateTime: "MMMM d, y 'at' p",
|
||||||
},
|
},
|
||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
@@ -20,7 +22,7 @@ export default {
|
|||||||
administrator: 'Administrator',
|
administrator: 'Administrator',
|
||||||
all: 'All',
|
all: 'All',
|
||||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||||
'All changes will be automatically saved<br />after connection restored',
|
'All changes will be automatically saved<br />after connection restored.',
|
||||||
areYouSureYouWantToDeleteThisAttachment: 'Are you sure you want to delete this attachment?',
|
areYouSureYouWantToDeleteThisAttachment: 'Are you sure you want to delete this attachment?',
|
||||||
areYouSureYouWantToDeleteThisBoard: 'Are you sure you want to delete this board?',
|
areYouSureYouWantToDeleteThisBoard: 'Are you sure you want to delete this board?',
|
||||||
areYouSureYouWantToDeleteThisCard: 'Are you sure you want to delete this card?',
|
areYouSureYouWantToDeleteThisCard: 'Are you sure you want to delete this card?',
|
||||||
@@ -43,15 +45,15 @@ export default {
|
|||||||
board: 'Board',
|
board: 'Board',
|
||||||
boardNotFound_title: 'Board Not Found',
|
boardNotFound_title: 'Board Not Found',
|
||||||
canComment: 'Can comment',
|
canComment: 'Can comment',
|
||||||
canEditContentOfBoard: 'Can edit the content of the board',
|
canEditContentOfBoard: 'Can edit the content of the board.',
|
||||||
canOnlyViewBoard: 'Can only view the board',
|
canOnlyViewBoard: 'Can only view the board.',
|
||||||
cardActions_title: 'Card Actions',
|
cardActions_title: 'Card Actions',
|
||||||
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',
|
||||||
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.',
|
||||||
createProject_title: 'Create Project',
|
createProject_title: 'Create Project',
|
||||||
createTextFile_title: 'Create Text File',
|
createTextFile_title: 'Create Text File',
|
||||||
currentPassword: 'Current password',
|
currentPassword: 'Current password',
|
||||||
@@ -80,11 +82,11 @@ export default {
|
|||||||
editLabel_title: 'Edit Label',
|
editLabel_title: 'Edit Label',
|
||||||
editPassword_title: 'Edit Password',
|
editPassword_title: 'Edit Password',
|
||||||
editPermissions_title: 'Edit Permissions',
|
editPermissions_title: 'Edit Permissions',
|
||||||
editTimer_title: 'Edit Timer',
|
editStopwatch_title: 'Edit Stopwatch',
|
||||||
editUsername_title: 'Edit Username',
|
editUsername_title: 'Edit Username',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail already in use',
|
emailAlreadyInUse: 'E-mail already in use',
|
||||||
enterCardTitle: 'Enter card title... [Ctrl+Enter] to auto-open',
|
enterCardTitle: 'Enter card title... [Ctrl+Enter] to auto-open.',
|
||||||
enterDescription: 'Enter description...',
|
enterDescription: 'Enter description...',
|
||||||
enterFilename: 'Enter filename',
|
enterFilename: 'Enter filename',
|
||||||
enterListTitle: 'Enter list title...',
|
enterListTitle: 'Enter list title...',
|
||||||
@@ -117,14 +119,14 @@ export default {
|
|||||||
noLists: 'No lists',
|
noLists: 'No lists',
|
||||||
noProjects: 'No projects',
|
noProjects: 'No projects',
|
||||||
notifications: 'Notifications',
|
notifications: 'Notifications',
|
||||||
noUnreadNotifications: 'No unread notifications',
|
noUnreadNotifications: 'No unread notifications.',
|
||||||
openBoard_title: 'Open Board',
|
openBoard_title: 'Open Board',
|
||||||
optional_inline: 'optional',
|
optional_inline: 'optional',
|
||||||
organization: 'Organization',
|
organization: 'Organization',
|
||||||
phone: 'Phone',
|
phone: 'Phone',
|
||||||
preferences: 'Preferences',
|
preferences: 'Preferences',
|
||||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||||
'Tip: press Ctrl-V (Cmd-V on Mac) to add an attachment from the clipboard',
|
'Tip: press Ctrl-V (Cmd-V on Mac) to add an attachment from the clipboard.',
|
||||||
project: 'Project',
|
project: 'Project',
|
||||||
projectNotFound_title: 'Project Not Found',
|
projectNotFound_title: 'Project Not Found',
|
||||||
removeManager_title: 'Remove Manager',
|
removeManager_title: 'Remove Manager',
|
||||||
@@ -138,13 +140,13 @@ export default {
|
|||||||
selectPermissions_title: 'Select Permissions',
|
selectPermissions_title: 'Select Permissions',
|
||||||
selectProject: 'Select project',
|
selectProject: 'Select project',
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
|
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',
|
||||||
tasks: 'Tasks',
|
tasks: 'Tasks',
|
||||||
thereIsNoPreviewAvailableForThisAttachment:
|
thereIsNoPreviewAvailableForThisAttachment:
|
||||||
'There is no preview available for this attachment',
|
'There is no preview available for this attachment.',
|
||||||
time: 'Time',
|
time: 'Time',
|
||||||
timer: 'Timer',
|
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
userActions_title: 'User Actions',
|
userActions_title: 'User Actions',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> added this card to {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> added this card to {{list}}</1>',
|
||||||
@@ -201,7 +203,7 @@ export default {
|
|||||||
editInformation_title: 'Edit Information',
|
editInformation_title: 'Edit Information',
|
||||||
editPassword_title: 'Edit Password',
|
editPassword_title: 'Edit Password',
|
||||||
editPermissions: 'Edit permissions',
|
editPermissions: 'Edit permissions',
|
||||||
editTimer_title: 'Edit Timer',
|
editStopwatch_title: 'Edit Stopwatch',
|
||||||
editTitle_title: 'Edit Title',
|
editTitle_title: 'Edit Title',
|
||||||
editUsername_title: 'Edit Username',
|
editUsername_title: 'Edit Username',
|
||||||
hideDetails: 'Hide details',
|
hideDetails: 'Hide details',
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ export default {
|
|||||||
projectManagement: 'Project management',
|
projectManagement: 'Project management',
|
||||||
serverConnectionFailed: 'Server connection failed',
|
serverConnectionFailed: 'Server connection failed',
|
||||||
unknownError: 'Unknown error, try again later',
|
unknownError: 'Unknown error, try again later',
|
||||||
|
useSingleSignOn: 'Use single sign-on',
|
||||||
},
|
},
|
||||||
|
|
||||||
action: {
|
action: {
|
||||||
logIn: 'Log in',
|
logIn: 'Log in',
|
||||||
|
logInWithSSO: 'Log in with SSO',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export default {
|
|||||||
dateTime: '$t(format:date) $t(format:time)',
|
dateTime: '$t(format:date) $t(format:time)',
|
||||||
longDate: 'MMM d',
|
longDate: 'MMM d',
|
||||||
longDateTime: "MMMM d 'a' p",
|
longDateTime: "MMMM d 'a' p",
|
||||||
|
fullDate: 'MMM d, y',
|
||||||
|
fullDateTime: "MMMM d, y 'a' p",
|
||||||
},
|
},
|
||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
@@ -22,7 +24,7 @@ export default {
|
|||||||
administrator: 'Administrador',
|
administrator: 'Administrador',
|
||||||
all: 'Todos',
|
all: 'Todos',
|
||||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||||
'Todos los cambios serán automáticamente guardados<br />después de que se restaure la conexión',
|
'Todos los cambios serán automáticamente guardados<br />después de que se restaure la conexión.',
|
||||||
areYouSureYouWantToDeleteThisAttachment: '¿Estás seguro de que quieres borrar este adjunto?',
|
areYouSureYouWantToDeleteThisAttachment: '¿Estás seguro de que quieres borrar este adjunto?',
|
||||||
areYouSureYouWantToDeleteThisBoard: '¿Estás seguro de que quieres borrar este tablero?',
|
areYouSureYouWantToDeleteThisBoard: '¿Estás seguro de que quieres borrar este tablero?',
|
||||||
areYouSureYouWantToDeleteThisCard: '¿Estás seguro de que quieres borrar esta tarjeta?',
|
areYouSureYouWantToDeleteThisCard: '¿Estás seguro de que quieres borrar esta tarjeta?',
|
||||||
@@ -41,11 +43,11 @@ export default {
|
|||||||
boardNotFound_title: 'Tablero no encontrado',
|
boardNotFound_title: 'Tablero no encontrado',
|
||||||
cardActions_title: 'Acciones de las tarjetas',
|
cardActions_title: 'Acciones de las tarjetas',
|
||||||
cardNotFound_title: 'tarjeta no encontrada',
|
cardNotFound_title: 'tarjeta no encontrada',
|
||||||
cardOrActionAreDeleted: 'La tarjeta o la acción está eliminada',
|
cardOrActionAreDeleted: 'La tarjeta o la acción está eliminada.',
|
||||||
color: 'Color',
|
color: 'Color',
|
||||||
createBoard_title: 'Crear Tablero',
|
createBoard_title: 'Crear Tablero',
|
||||||
createLabel_title: 'Crear Etiqueta',
|
createLabel_title: 'Crear Etiqueta',
|
||||||
createNewOneOrSelectExistingOne: 'Crear uno nuevo o seleccionar<br /> uno existente',
|
createNewOneOrSelectExistingOne: 'Crear uno nuevo o seleccionar<br />uno existente.',
|
||||||
createProject_title: 'Crear Proyecto',
|
createProject_title: 'Crear Proyecto',
|
||||||
createTextFile_title: 'Crear archivo de Texto',
|
createTextFile_title: 'Crear archivo de Texto',
|
||||||
currentPassword: 'Contraseña Actual',
|
currentPassword: 'Contraseña Actual',
|
||||||
@@ -69,7 +71,7 @@ export default {
|
|||||||
editEmail_title: 'Editar Correo',
|
editEmail_title: 'Editar Correo',
|
||||||
editLabel_title: 'Editar Etiqueta',
|
editLabel_title: 'Editar Etiqueta',
|
||||||
editPassword_title: 'Editar Contraseña',
|
editPassword_title: 'Editar Contraseña',
|
||||||
editTimer_title: 'Editar Temporizador',
|
editStopwatch_title: 'Editar Temporizador',
|
||||||
editUsername_title: 'Edit nombre de usuario',
|
editUsername_title: 'Edit nombre de usuario',
|
||||||
email: 'Correo',
|
email: 'Correo',
|
||||||
emailAlreadyInUse: 'El correo ya está en uso',
|
emailAlreadyInUse: 'El correo ya está en uso',
|
||||||
@@ -106,7 +108,7 @@ export default {
|
|||||||
phone: 'Teléfono',
|
phone: 'Teléfono',
|
||||||
preferences: 'Preferencias',
|
preferences: 'Preferencias',
|
||||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||||
'Tip: presiona Ctrl-V (Cmd-V en Mac) para añadir adjuntos desde el portapapeles',
|
'Tip: presiona Ctrl-V (Cmd-V en Mac) para añadir adjuntos desde el portapapeles.',
|
||||||
project: 'Proyecto',
|
project: 'Proyecto',
|
||||||
projectNotFound_title: 'Proyecto no encontrado',
|
projectNotFound_title: 'Proyecto no encontrado',
|
||||||
removeMember_title: 'Remover Miembro',
|
removeMember_title: 'Remover Miembro',
|
||||||
@@ -115,11 +117,11 @@ export default {
|
|||||||
selectList: 'Seleccionar Lista',
|
selectList: 'Seleccionar Lista',
|
||||||
selectProject: 'Seleccionar Proyecto',
|
selectProject: 'Seleccionar Proyecto',
|
||||||
settings: 'Configuraciones',
|
settings: 'Configuraciones',
|
||||||
|
stopwatch: 'Temporizador',
|
||||||
subscribeToMyOwnCardsByDefault: 'Suscribirme a mis propias tarjetas por defecto',
|
subscribeToMyOwnCardsByDefault: 'Suscribirme a mis propias tarjetas por defecto',
|
||||||
taskActions_title: 'Acciones de Tareas',
|
taskActions_title: 'Acciones de Tareas',
|
||||||
tasks: 'Tareas',
|
tasks: 'Tareas',
|
||||||
time: 'Tiempo',
|
time: 'Tiempo',
|
||||||
timer: 'Temporizador',
|
|
||||||
title: 'Título',
|
title: 'Título',
|
||||||
userActions_title: 'Acciones de Usuario',
|
userActions_title: 'Acciones de Usuario',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> añadido a esta tarjeta en {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> añadido a esta tarjeta en {{list}}</1>',
|
||||||
@@ -172,7 +174,7 @@ export default {
|
|||||||
editDescription_title: 'Editar Descripción',
|
editDescription_title: 'Editar Descripción',
|
||||||
editEmail_title: 'Editar Correo',
|
editEmail_title: 'Editar Correo',
|
||||||
editPassword_title: 'Editar Contraseña',
|
editPassword_title: 'Editar Contraseña',
|
||||||
editTimer_title: 'Edit Temporizador',
|
editStopwatch_title: 'Edit Temporizador',
|
||||||
editTitle_title: 'Edit Título',
|
editTitle_title: 'Edit Título',
|
||||||
editUsername_title: 'Editar Nombre de Usuario',
|
editUsername_title: 'Editar Nombre de Usuario',
|
||||||
logOut_title: 'Cerrar Sesión',
|
logOut_title: 'Cerrar Sesión',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export default {
|
|||||||
dateTime: '$t(format:date) $t(format:time)',
|
dateTime: '$t(format:date) $t(format:time)',
|
||||||
longDate: 'd MMM',
|
longDate: 'd MMM',
|
||||||
longDateTime: "d MMMM 'à' p",
|
longDateTime: "d MMMM 'à' p",
|
||||||
|
fullDate: 'd MMM y',
|
||||||
|
fullDateTime: "d MMMM y 'à' p",
|
||||||
},
|
},
|
||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
@@ -22,7 +24,7 @@ export default {
|
|||||||
administrator: 'Administrateur',
|
administrator: 'Administrateur',
|
||||||
all: 'Tout',
|
all: 'Tout',
|
||||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||||
'Toutes les modifications seront automatiquement enregistrées <br /> une fois la connexion rétablie',
|
'Toutes les modifications seront automatiquement enregistrées<br />une fois la connexion rétablie.',
|
||||||
areYouSureYouWantToDeleteThisAttachment: 'Voulez-vous vraiment supprimer cette pièce jointe?',
|
areYouSureYouWantToDeleteThisAttachment: 'Voulez-vous vraiment supprimer cette pièce jointe?',
|
||||||
areYouSureYouWantToDeleteThisBoard: 'Êtes-vous sûr de vouloir supprimer ce forum?',
|
areYouSureYouWantToDeleteThisBoard: 'Êtes-vous sûr de vouloir supprimer ce forum?',
|
||||||
areYouSureYouWantToDeleteThisCard: 'Voulez-vous vraiment supprimer cette carte?',
|
areYouSureYouWantToDeleteThisCard: 'Voulez-vous vraiment supprimer cette carte?',
|
||||||
@@ -41,11 +43,11 @@ export default {
|
|||||||
boardNotFound_title: 'Carte non trouvée',
|
boardNotFound_title: 'Carte non trouvée',
|
||||||
cardActions_title: 'Actions sur la carte',
|
cardActions_title: 'Actions sur la carte',
|
||||||
cardNotFound_title: 'Carte non trouvée',
|
cardNotFound_title: 'Carte non trouvée',
|
||||||
cardOrActionAreDeleted: "La carte ou l'action sont supprimées",
|
cardOrActionAreDeleted: "La carte ou l'action sont supprimées.",
|
||||||
color: 'Couleur',
|
color: 'Couleur',
|
||||||
createBoard_title: 'Créer un tableau',
|
createBoard_title: 'Créer un tableau',
|
||||||
createLabel_title: 'Créer une étiquette',
|
createLabel_title: 'Créer une étiquette',
|
||||||
createNewOneOrSelectExistingOne: 'Créez-en un nouveau ou sélectionnez <br /> un existant',
|
createNewOneOrSelectExistingOne: 'Créez-en un nouveau ou sélectionnez<br />un existant.',
|
||||||
createProject_title: 'Créer un projet',
|
createProject_title: 'Créer un projet',
|
||||||
createTextFile_title: 'Créer un fichier texte',
|
createTextFile_title: 'Créer un fichier texte',
|
||||||
currentPassword: 'Mot de passe actuel',
|
currentPassword: 'Mot de passe actuel',
|
||||||
@@ -69,7 +71,7 @@ export default {
|
|||||||
editEmail_title: "Modifier l'e-mail",
|
editEmail_title: "Modifier l'e-mail",
|
||||||
editLabel_title: "Modifier l'étiquette",
|
editLabel_title: "Modifier l'étiquette",
|
||||||
editPassword_title: 'Modifier le mot de passe',
|
editPassword_title: 'Modifier le mot de passe',
|
||||||
editTimer_title: 'Modifier la minuterie',
|
editStopwatch_title: 'Modifier la minuterie',
|
||||||
editUsername_title: "Modifier le nom d'utilisateur",
|
editUsername_title: "Modifier le nom d'utilisateur",
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'Email déjà utilisé',
|
emailAlreadyInUse: 'Email déjà utilisé',
|
||||||
@@ -99,7 +101,7 @@ export default {
|
|||||||
noLists: 'Pas de listes',
|
noLists: 'Pas de listes',
|
||||||
noProjects: 'Pas de projets',
|
noProjects: 'Pas de projets',
|
||||||
notifications: 'Notifications',
|
notifications: 'Notifications',
|
||||||
noUnreadNotifications: 'Aucune notification non lue',
|
noUnreadNotifications: 'Aucune notification non lue.',
|
||||||
openBoard_title: 'Open Board',
|
openBoard_title: 'Open Board',
|
||||||
optional_inline: 'optionnel',
|
optional_inline: 'optionnel',
|
||||||
organization: 'Organisation',
|
organization: 'Organisation',
|
||||||
@@ -114,11 +116,11 @@ export default {
|
|||||||
selectList: 'Sélectionner une liste',
|
selectList: 'Sélectionner une liste',
|
||||||
selectProject: 'Sélectionner un projet',
|
selectProject: 'Sélectionner un projet',
|
||||||
settings: 'Réglages',
|
settings: 'Réglages',
|
||||||
|
stopwatch: 'Minuteur',
|
||||||
subscribeToMyOwnCardsByDefault: 'Abonnez-vous à mes propres cartes par défaut',
|
subscribeToMyOwnCardsByDefault: 'Abonnez-vous à mes propres cartes par défaut',
|
||||||
taskActions_title: 'Actions de tâche',
|
taskActions_title: 'Actions de tâche',
|
||||||
tasks: 'Tâches',
|
tasks: 'Tâches',
|
||||||
time: 'Temps',
|
time: 'Temps',
|
||||||
timer: 'Minuteur',
|
|
||||||
title: 'Titre',
|
title: 'Titre',
|
||||||
userActions_title: "Actions de l'utilisateur",
|
userActions_title: "Actions de l'utilisateur",
|
||||||
userAddedThisCardToList: '<0> {{user}} </0> <1> a ajouté cette carte à {{list}} </1>',
|
userAddedThisCardToList: '<0> {{user}} </0> <1> a ajouté cette carte à {{list}} </1>',
|
||||||
@@ -172,7 +174,7 @@ export default {
|
|||||||
editDescription_title: 'Éditer la description',
|
editDescription_title: 'Éditer la description',
|
||||||
editEmail_title: "Modifier l'e-mail",
|
editEmail_title: "Modifier l'e-mail",
|
||||||
editPassword_title: 'Modifier le mot de passe',
|
editPassword_title: 'Modifier le mot de passe',
|
||||||
editTimer_title: 'Modifier la minuterie',
|
editStopwatch_title: 'Modifier la minuterie',
|
||||||
editTitle_title: 'Modifier le titre',
|
editTitle_title: 'Modifier le titre',
|
||||||
editUsername_title: "Modifier le nom d'utilisateur",
|
editUsername_title: "Modifier le nom d'utilisateur",
|
||||||
logOut_title: 'Se déconnecter',
|
logOut_title: 'Se déconnecter',
|
||||||
|
|||||||
@@ -8,13 +8,16 @@ import it from './it';
|
|||||||
import ja from './ja';
|
import ja from './ja';
|
||||||
import ko from './ko';
|
import ko from './ko';
|
||||||
import pl from './pl';
|
import pl from './pl';
|
||||||
|
import ro from './ro';
|
||||||
import ru from './ru';
|
import ru from './ru';
|
||||||
import sk from './sk';
|
import sk from './sk';
|
||||||
import sv from './sv';
|
import sv from './sv';
|
||||||
|
import tr from './tr';
|
||||||
|
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, ru, sk, sv, uz, zh];
|
const locales = [cs, da, de, en, es, fr, it, ja, ko, pl, ro, ru, sk, sv, tr, ua, uz, zh];
|
||||||
|
|
||||||
export default locales;
|
export default locales;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export default {
|
|||||||
dateTime: '$t(format:date) $t(format:time)',
|
dateTime: '$t(format:date) $t(format:time)',
|
||||||
longDate: 'MMM d',
|
longDate: 'MMM d',
|
||||||
longDateTime: "MMMM d 'at' p",
|
longDateTime: "MMMM d 'at' p",
|
||||||
|
fullDate: 'MMM d, y',
|
||||||
|
fullDateTime: "MMMM d, y 'at' p",
|
||||||
},
|
},
|
||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
@@ -19,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',
|
'All changes will be automatically saved<br />after connection restored.',
|
||||||
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?',
|
||||||
@@ -42,15 +44,15 @@ export default {
|
|||||||
board: 'Bacheca',
|
board: 'Bacheca',
|
||||||
boardNotFound_title: 'Bacheca non trovata',
|
boardNotFound_title: 'Bacheca non trovata',
|
||||||
canComment: 'Può commentare',
|
canComment: 'Può commentare',
|
||||||
canEditContentOfBoard: 'Può modificare il contenuto della bacheca',
|
canEditContentOfBoard: 'Può modificare il contenuto della bacheca.',
|
||||||
canOnlyViewBoard: 'Può solo vedere la bacheca',
|
canOnlyViewBoard: 'Può solo vedere la bacheca.',
|
||||||
cardActions_title: 'Card Azioni',
|
cardActions_title: 'Card Azioni',
|
||||||
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',
|
||||||
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.',
|
||||||
createProject_title: 'Crea Progetto',
|
createProject_title: 'Crea Progetto',
|
||||||
createTextFile_title: 'Create File di Testo',
|
createTextFile_title: 'Create File di Testo',
|
||||||
currentPassword: 'Password corrente',
|
currentPassword: 'Password corrente',
|
||||||
@@ -79,7 +81,7 @@ export default {
|
|||||||
editLabel_title: 'Modifica Etichetta',
|
editLabel_title: 'Modifica Etichetta',
|
||||||
editPassword_title: 'Modifica Password',
|
editPassword_title: 'Modifica Password',
|
||||||
editPermissions_title: 'Modifica Permessi',
|
editPermissions_title: 'Modifica Permessi',
|
||||||
editTimer_title: 'Modifica Timer',
|
editStopwatch_title: 'Modifica Timer',
|
||||||
editUsername_title: 'Modifica Username',
|
editUsername_title: 'Modifica Username',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail già in uso',
|
emailAlreadyInUse: 'E-mail già in uso',
|
||||||
@@ -114,14 +116,14 @@ export default {
|
|||||||
noLists: 'No liste',
|
noLists: 'No liste',
|
||||||
noProjects: 'No progetti',
|
noProjects: 'No progetti',
|
||||||
notifications: 'Notifiche',
|
notifications: 'Notifiche',
|
||||||
noUnreadNotifications: 'Nessuna notifica non letta',
|
noUnreadNotifications: 'Nessuna notifica non letta.',
|
||||||
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',
|
'Tip: prmi 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',
|
||||||
@@ -135,13 +137,13 @@ export default {
|
|||||||
selectPermissions_title: 'Seleziona Permessi',
|
selectPermissions_title: 'Seleziona Permessi',
|
||||||
selectProject: 'Seleziona progetto',
|
selectProject: 'Seleziona progetto',
|
||||||
settings: 'Impostazioni',
|
settings: 'Impostazioni',
|
||||||
|
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',
|
||||||
tasks: 'Tasks',
|
tasks: 'Tasks',
|
||||||
thereIsNoPreviewAvailableForThisAttachment:
|
thereIsNoPreviewAvailableForThisAttachment:
|
||||||
'Non è disponibile alcuna anteprima per questo allegato',
|
'Non è disponibile alcuna anteprima per questo allegato.',
|
||||||
time: 'Tempo',
|
time: 'Tempo',
|
||||||
timer: 'Timer',
|
|
||||||
title: 'Titolo',
|
title: 'Titolo',
|
||||||
userActions_title: 'Azioni Utente',
|
userActions_title: 'Azioni Utente',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> ha aggiunto questa card a {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> ha aggiunto questa card a {{list}}</1>',
|
||||||
@@ -198,7 +200,7 @@ export default {
|
|||||||
editInformation_title: 'Modifca Informazioni',
|
editInformation_title: 'Modifca Informazioni',
|
||||||
editPassword_title: 'Modifica Password',
|
editPassword_title: 'Modifica Password',
|
||||||
editPermissions: 'Modifica permessi',
|
editPermissions: 'Modifica permessi',
|
||||||
editTimer_title: 'Modifica Timer',
|
editStopwatch_title: 'Modifica Timer',
|
||||||
editTitle_title: 'Modifica Titolo',
|
editTitle_title: 'Modifica Titolo',
|
||||||
editUsername_title: 'Modifica Username',
|
editUsername_title: 'Modifica Username',
|
||||||
hideDetails: 'Nascondi dettagli',
|
hideDetails: 'Nascondi dettagli',
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ export default {
|
|||||||
time: 'HH:mm',
|
time: 'HH:mm',
|
||||||
dateTime: '$t(format:date) $t(format:time)',
|
dateTime: '$t(format:date) $t(format:time)',
|
||||||
longDate: 'MMMMd日',
|
longDate: 'MMMMd日',
|
||||||
longDateTime: "MMMMd'日 ' HH:MM",
|
longDateTime: "MMMMd'日 ' HH:mm",
|
||||||
|
fullDate: 'yyyy年M月d日',
|
||||||
|
fullDateTime: 'yyyy年M月d日 HH:mm',
|
||||||
},
|
},
|
||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
|
aboutPlanka: 'Plankaについて',
|
||||||
account: 'アカウント',
|
account: 'アカウント',
|
||||||
actions: 'アクション',
|
actions: 'アクション',
|
||||||
addAttachment_title: '添付ファイルを追加',
|
addAttachment_title: '添付ファイルを追加',
|
||||||
@@ -23,7 +26,7 @@ export default {
|
|||||||
administrator: '管理者',
|
administrator: '管理者',
|
||||||
all: 'すべて',
|
all: 'すべて',
|
||||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||||
'全ての変更は接続回復後自動的に保存されます',
|
'全ての変更は接続回復後自動的に保存されます。',
|
||||||
areYouSureYouWantToDeleteThisAttachment: 'この添付ファイルを削除してもよろしいですか?',
|
areYouSureYouWantToDeleteThisAttachment: 'この添付ファイルを削除してもよろしいですか?',
|
||||||
areYouSureYouWantToDeleteThisBoard: 'このボードを削除してもよろしいですか?',
|
areYouSureYouWantToDeleteThisBoard: 'このボードを削除してもよろしいですか?',
|
||||||
areYouSureYouWantToDeleteThisCard: 'このカードを削除してもよろしいですか?',
|
areYouSureYouWantToDeleteThisCard: 'このカードを削除してもよろしいですか?',
|
||||||
@@ -45,13 +48,16 @@ export default {
|
|||||||
background: '背景',
|
background: '背景',
|
||||||
board: 'ボード',
|
board: 'ボード',
|
||||||
boardNotFound_title: 'ボードが見つかりません',
|
boardNotFound_title: 'ボードが見つかりません',
|
||||||
|
canComment: 'コメントを作成することができます',
|
||||||
|
canEditContentOfBoard: 'ボードコンテンツを修正することができます。',
|
||||||
|
canOnlyViewBoard: 'ボードの読み取りのみ可能です。',
|
||||||
cardActions_title: 'カードのアクション',
|
cardActions_title: 'カードのアクション',
|
||||||
cardNotFound_title: 'カードが見つかりません',
|
cardNotFound_title: 'カードが見つかりません',
|
||||||
cardOrActionAreDeleted: 'カードやアクションが削除されます',
|
cardOrActionAreDeleted: 'カードやアクションが削除されます。',
|
||||||
color: '色',
|
color: '色',
|
||||||
createBoard_title: 'ボードを作成',
|
createBoard_title: 'ボードを作成',
|
||||||
createLabel_title: 'ラベルを作成',
|
createLabel_title: 'ラベルを作成',
|
||||||
createNewOneOrSelectExistingOne: '新規作成もしくは既存のものから選択',
|
createNewOneOrSelectExistingOne: '新規作成もしくは<br />既存のものから選択。',
|
||||||
createProject_title: 'プロジェクトを作成',
|
createProject_title: 'プロジェクトを作成',
|
||||||
createTextFile_title: 'テキストファイルを作成',
|
createTextFile_title: 'テキストファイルを作成',
|
||||||
currentPassword: '現在のパスワード',
|
currentPassword: '現在のパスワード',
|
||||||
@@ -68,31 +74,38 @@ export default {
|
|||||||
deleteTask_title: 'タスクを削除',
|
deleteTask_title: 'タスクを削除',
|
||||||
deleteUser_title: 'ユーザーを削除',
|
deleteUser_title: 'ユーザーを削除',
|
||||||
description: '説明',
|
description: '説明',
|
||||||
|
detectAutomatically: '自動的に検知',
|
||||||
dropFileToUpload: 'ファイルをドロップしてアップロード',
|
dropFileToUpload: 'ファイルをドロップしてアップロード',
|
||||||
|
editor: 'エディター',
|
||||||
editAttachment_title: '添付ファイルを編集',
|
editAttachment_title: '添付ファイルを編集',
|
||||||
editAvatar_title: 'アバターを編集',
|
editAvatar_title: 'アバターを編集',
|
||||||
editBoard_title: 'ボードを編集',
|
editBoard_title: 'ボードを編集',
|
||||||
editDueDate_title: '期限を編集',
|
editDueDate_title: '期限を編集',
|
||||||
editEmail_title: 'Eメールを編集',
|
editEmail_title: 'Eメールを編集',
|
||||||
|
editInformation_title: '情報を編集',
|
||||||
editLabel_title: 'ラベルを編集',
|
editLabel_title: 'ラベルを編集',
|
||||||
editPassword_title: 'パスワードを編集',
|
editPassword_title: 'パスワードを編集',
|
||||||
editTimer_title: 'タイマーを編集',
|
editPermissions_title: '権限を編集',
|
||||||
|
editStopwatch_title: 'タイマーを編集',
|
||||||
editUsername_title: 'ユーザー名を編集',
|
editUsername_title: 'ユーザー名を編集',
|
||||||
email: 'Eメール',
|
email: 'Eメール',
|
||||||
emailAlreadyInUse: 'Eメールは既に使われています',
|
emailAlreadyInUse: 'Eメールは既に使われています',
|
||||||
enterCardTitle: 'カードのタイトルを入力...',
|
enterCardTitle: 'カードのタイトルを入力… [Ctrl+Enter]で自動的に開く。',
|
||||||
enterDescription: '説明を入力...',
|
enterDescription: '説明を入力…',
|
||||||
enterFilename: 'ファイル名を入力...',
|
enterFilename: 'ファイル名を入力',
|
||||||
enterListTitle: 'リストのタイトルを入力...',
|
enterListTitle: 'リストのタイトルを入力…',
|
||||||
enterProjectTitle: 'プロジェクトのタイトルを入力',
|
enterProjectTitle: 'プロジェクトのタイトルを入力',
|
||||||
enterTaskDescription: 'タスクの説明を入力...',
|
enterTaskDescription: 'タスクの説明を入力…',
|
||||||
filterByLabels_title: 'ラベルで絞り込む',
|
filterByLabels_title: 'ラベルで絞り込む',
|
||||||
filterByMembers_title: 'メンバーで絞り込む',
|
filterByMembers_title: 'メンバーで絞り込む',
|
||||||
fromComputer_title: 'コンピューターから',
|
fromComputer_title: 'コンピューターから',
|
||||||
|
fromTrello: 'Trelloから',
|
||||||
general: '一般',
|
general: '一般',
|
||||||
hours: '時間',
|
hours: '時間',
|
||||||
|
importBoard_title: 'インポートボード',
|
||||||
invalidCurrentPassword: '現在のパスワードが無効',
|
invalidCurrentPassword: '現在のパスワードが無効',
|
||||||
labels: 'ラベル',
|
labels: 'ラベル',
|
||||||
|
language: '使用言語',
|
||||||
leaveBoard_title: 'ボードから退出',
|
leaveBoard_title: 'ボードから退出',
|
||||||
leaveProject_title: 'プロジェクトから退出',
|
leaveProject_title: 'プロジェクトから退出',
|
||||||
list: 'リスト',
|
list: 'リスト',
|
||||||
@@ -110,41 +123,48 @@ export default {
|
|||||||
noLists: 'リストがありません',
|
noLists: 'リストがありません',
|
||||||
noProjects: 'プロジェクトがありません',
|
noProjects: 'プロジェクトがありません',
|
||||||
notifications: '通知',
|
notifications: '通知',
|
||||||
noUnreadNotifications: '未読の通知はありません',
|
noUnreadNotifications: '未読の通知はありません。',
|
||||||
openBoard_title: 'ボードを開く',
|
openBoard_title: 'ボードを開く',
|
||||||
optional_inline: '任意',
|
optional_inline: '任意',
|
||||||
organization: '組織',
|
organization: '組織',
|
||||||
phone: '電話番号',
|
phone: '電話番号',
|
||||||
preferences: '環境設定',
|
preferences: '環境設定',
|
||||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||||
'ヒント: Ctrl-V(MacではCmd-V)を押して、クリップボードから添付ファイルを追加します',
|
'ヒント: Ctrl-V(MacではCmd-V)を押して、クリップボードから添付ファイルを追加します。',
|
||||||
project: 'プロジェクト',
|
project: 'プロジェクト',
|
||||||
projectNotFound_title: 'プロジェクトがありません',
|
projectNotFound_title: 'プロジェクトがありません',
|
||||||
removeManager_title: 'マネージャーを削除',
|
removeManager_title: 'マネージャーを削除',
|
||||||
removeMember_title: 'メンバーを削除',
|
removeMember_title: 'メンバーを削除',
|
||||||
|
searchLabels: 'ラベルから探す…',
|
||||||
|
searchMembers: 'メンバーから探す…',
|
||||||
|
searchUsers: 'ユーザーから探す…',
|
||||||
seconds: '秒',
|
seconds: '秒',
|
||||||
selectBoard: 'ボードを選択',
|
selectBoard: 'ボードを選択',
|
||||||
selectList: 'リストを選択',
|
selectList: 'リストを選択',
|
||||||
|
selectPermissions_title: '権限を選択',
|
||||||
selectProject: 'プロジェクトを選択',
|
selectProject: 'プロジェクトを選択',
|
||||||
settings: '設定',
|
settings: '設定',
|
||||||
|
stopwatch: 'タイマー',
|
||||||
subscribeToMyOwnCardsByDefault: '自分のカードをデフォルトで購読する',
|
subscribeToMyOwnCardsByDefault: '自分のカードをデフォルトで購読する',
|
||||||
taskActions_title: 'タスクのアクション',
|
taskActions_title: 'タスクのアクション',
|
||||||
tasks: 'タスク',
|
tasks: 'タスク',
|
||||||
|
thereIsNoPreviewAvailableForThisAttachment: 'この添付ファイルにはプレビューがありません。',
|
||||||
time: '時間',
|
time: '時間',
|
||||||
timer: 'タイマー',
|
|
||||||
title: 'タイトル',
|
title: 'タイトル',
|
||||||
userActions_title: 'ユーザーのアクション',
|
userActions_title: 'ユーザーのアクション',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0> は <1>{{list}} をこのカードに追加しました</1>',
|
userAddedThisCardToList: '<0>{{user}}</0> 様が <1>{{list}} をこのカードに追加しました</1>',
|
||||||
userLeftNewCommentToCard:
|
userLeftNewCommentToCard:
|
||||||
'{{user}} は <2> {{card}} </2> に新しいコメント «{{comment}}» を残しました',
|
'{{user}} 様が <2> {{card}} </2> に新しいコメント «{{comment}}» を残しました',
|
||||||
userMovedCardFromListToList:
|
userMovedCardFromListToList:
|
||||||
'{{user}} は <2> {{card}} </2> を {{fromList}} から {{toList}} に移動しました',
|
'{{user}} 様が <2> {{card}} </2> を {{fromList}} から {{toList}} に移動しました',
|
||||||
userMovedThisCardFromListToList:
|
userMovedThisCardFromListToList:
|
||||||
'<0>{{user}}</0> <1>はこのカードを {{fromList}} から {{toList}} に移動しました</1>',
|
'<0>{{user}}</0> <1>様がこのカードを {{fromList}} から {{toList}} に移動しました</1>',
|
||||||
username: 'ユーザー名',
|
username: 'ユーザー名',
|
||||||
usernameAlreadyInUse: 'ユーザー名は既に使われています',
|
usernameAlreadyInUse: 'ユーザー名は既に使われています',
|
||||||
users: 'ユーザー',
|
users: 'ユーザー',
|
||||||
writeComment: 'コメントを書く...',
|
version: 'バージョン',
|
||||||
|
viewer: 'ビューア',
|
||||||
|
writeComment: 'コメントを書く…',
|
||||||
},
|
},
|
||||||
|
|
||||||
action: {
|
action: {
|
||||||
@@ -155,6 +175,7 @@ export default {
|
|||||||
addCard_title: 'カードを追加',
|
addCard_title: 'カードを追加',
|
||||||
addComment: 'コメントを追加',
|
addComment: 'コメントを追加',
|
||||||
addList: 'リストを追加',
|
addList: 'リストを追加',
|
||||||
|
addMember: 'メンバーを追加',
|
||||||
addMoreDetailedDescription: 'より詳細な説明を追加',
|
addMoreDetailedDescription: 'より詳細な説明を追加',
|
||||||
addTask: 'タスクを追加',
|
addTask: 'タスクを追加',
|
||||||
addToCard: 'カードに追加',
|
addToCard: 'カードに追加',
|
||||||
@@ -184,10 +205,14 @@ export default {
|
|||||||
editDueDate_title: '期限を編集',
|
editDueDate_title: '期限を編集',
|
||||||
editDescription_title: '説明を編集',
|
editDescription_title: '説明を編集',
|
||||||
editEmail_title: 'Eメールを編集',
|
editEmail_title: 'Eメールを編集',
|
||||||
|
editInformation_title: '情報を編集',
|
||||||
editPassword_title: 'パスワードの編集',
|
editPassword_title: 'パスワードの編集',
|
||||||
editTimer_title: 'タイマーの編集',
|
editPermissions: '権限を編集',
|
||||||
|
editStopwatch_title: 'タイマーの編集',
|
||||||
editTitle_title: 'タイトルの編集',
|
editTitle_title: 'タイトルの編集',
|
||||||
editUsername_title: 'ユーザー名の編集',
|
editUsername_title: 'ユーザー名の編集',
|
||||||
|
hideDetails: '詳細を隠す',
|
||||||
|
import: 'インポート',
|
||||||
leaveBoard: 'ボードから退出',
|
leaveBoard: 'ボードから退出',
|
||||||
leaveProject: 'プロジェクトから退出',
|
leaveProject: 'プロジェクトから退出',
|
||||||
logOut_title: 'ログアウト',
|
logOut_title: 'ログアウト',
|
||||||
@@ -203,6 +228,7 @@ export default {
|
|||||||
removeMember: 'メンバーを削除',
|
removeMember: 'メンバーを削除',
|
||||||
save: '保存',
|
save: '保存',
|
||||||
showAllAttachments: '全ての添付ファイルを表示する({{hidden}} 非表示)',
|
showAllAttachments: '全ての添付ファイルを表示する({{hidden}} 非表示)',
|
||||||
|
showDetails: '詳細を表示',
|
||||||
showFewerAttachments: 'テンプファイルの表示数を減らす',
|
showFewerAttachments: 'テンプファイルの表示数を減らす',
|
||||||
start: 'スタート',
|
start: 'スタート',
|
||||||
stop: 'ストップ',
|
stop: 'ストップ',
|
||||||
|
|||||||
@@ -5,16 +5,18 @@ export default {
|
|||||||
invalidEmailOrUsername: 'Eメールまたはユーザー名が無効',
|
invalidEmailOrUsername: 'Eメールまたはユーザー名が無効',
|
||||||
invalidPassword: 'パスワードが無効',
|
invalidPassword: 'パスワードが無効',
|
||||||
logInToPlanka: 'Planka にログインする',
|
logInToPlanka: 'Planka にログインする',
|
||||||
noInternetConnection: 'インターネットに接続されていません。',
|
noInternetConnection: 'インターネットに接続されていません',
|
||||||
pageNotFound_title: 'ページが見つかりません。',
|
pageNotFound_title: 'ページが見つかりません',
|
||||||
password: 'パスワード',
|
password: 'パスワード',
|
||||||
projectManagement: 'プロジェクト管理',
|
projectManagement: 'プロジェクト管理',
|
||||||
serverConnectionFailed: 'サーバーの接続に失敗',
|
serverConnectionFailed: 'サーバーの接続に失敗',
|
||||||
unknownError: '不明なエラーです。後でもう一度試してください。',
|
unknownError: '不明なエラーです。後でもう一度試してください。',
|
||||||
|
useSingleSignOn: 'SSOを使用',
|
||||||
},
|
},
|
||||||
|
|
||||||
action: {
|
action: {
|
||||||
logIn: 'ログイン',
|
logIn: 'ログイン',
|
||||||
|
logInWithSSO: 'SSOでログイン',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+141
-121
@@ -1,167 +1,183 @@
|
|||||||
import dateFns from 'date-fns/locale/ko';
|
import dateFns from 'date-fns/locale/ko';
|
||||||
|
|
||||||
/* Translated by Park Seok Ho */
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
dateFns,
|
dateFns,
|
||||||
|
|
||||||
format: {
|
format: {
|
||||||
date: 'yyyy/M/d',
|
date: 'yyyy/M/d',
|
||||||
time: 'HH:mm',
|
time: 'a hh시 mm분',
|
||||||
dateTime: '$t(format:date) $t(format:time)',
|
dateTime: '$t(format:date) $t(format:time)',
|
||||||
longDate: 'MMMMd일',
|
longDate: "MMMMd'일'",
|
||||||
longDateTime: "MMMMd'일 ' HH:MM",
|
longDateTime: "MMMMd'일 ' a hh시 mm분",
|
||||||
|
fullDate: 'yyyy년M월d일',
|
||||||
|
fullDateTime: 'yyyy년M월d일 a hh시 mm분',
|
||||||
},
|
},
|
||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
|
aboutPlanka: 'Planka에 대해서',
|
||||||
account: '계정',
|
account: '계정',
|
||||||
actions: '활동',
|
actions: '활동',
|
||||||
addAttachment_title: '첨부 파일 추가',
|
addAttachment_title: '첨부 파일 추가',
|
||||||
addComment: '댓글',
|
addComment: '댓글',
|
||||||
addManager_title: '관리자 추가',
|
addManager_title: '매니저 추가',
|
||||||
addMember_title: '회원 추가',
|
addMember_title: '멤버 추가',
|
||||||
addUser_title: '사용자 추가',
|
addUser_title: '사용자 추가',
|
||||||
administrator: '시스템 관리자',
|
administrator: '관리자',
|
||||||
all: 'All',
|
all: '모두',
|
||||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||||
'모든 변경 사항은 연결이 복원된 후 자동으로 저장됩니다',
|
'연결이 복원되면<br />모든 변경 사항이 자동으로 저장됩니다.',
|
||||||
areYouSureYouWantToDeleteThisAttachment: '첨부 파일을 삭제하시겠습니까?',
|
areYouSureYouWantToDeleteThisAttachment: '해당하는 첨부 파일을 삭제하시겠습니까?',
|
||||||
areYouSureYouWantToDeleteThisBoard: '보드를 삭제하시겠습니까?',
|
areYouSureYouWantToDeleteThisBoard: '해당하는 보드를 삭제하시겠습니까?',
|
||||||
areYouSureYouWantToDeleteThisCard: '항목카드를 삭제하시겠습니까?',
|
areYouSureYouWantToDeleteThisCard: '해당하는 항목카드를 삭제하시겠습니까?',
|
||||||
areYouSureYouWantToDeleteThisComment: '댓글을 삭제하시겠습니까?',
|
areYouSureYouWantToDeleteThisComment: '해당하는 댓글을 삭제하시겠습니까?',
|
||||||
areYouSureYouWantToDeleteThisLabel: '라벨을 삭제하시겠습니까?',
|
areYouSureYouWantToDeleteThisLabel: '해당하는 라벨을 삭제하시겠습니까?',
|
||||||
areYouSureYouWantToDeleteThisList: '리스트를 삭제하시겠습니까?',
|
areYouSureYouWantToDeleteThisList: '해당하는 목록를 삭제하시겠습니까?',
|
||||||
areYouSureYouWantToDeleteThisProject: '프로젝트를 삭제하시겠습니까?',
|
areYouSureYouWantToDeleteThisProject: '해당하는 프로젝트를 삭제하시겠습니까?',
|
||||||
areYouSureYouWantToDeleteThisTask: '태스트를 삭제하겠습니까?',
|
areYouSureYouWantToDeleteThisTask: '해당하는 업무를 삭제하겠습니까?',
|
||||||
areYouSureYouWantToDeleteThisUser: '이 유저를 삭제하겠습니까?',
|
areYouSureYouWantToDeleteThisUser: '해당하는 사용자를 삭제하겠습니까?',
|
||||||
areYouSureYouWantToLeaveBoard: '이 보드에서 나가시겠습니까?',
|
areYouSureYouWantToLeaveBoard: '이 보드에서 나가시겠습니까?',
|
||||||
areYouSureYouWantToLeaveProject: '이 프로젝트에서 나가시겠습니까?',
|
areYouSureYouWantToLeaveProject: '이 프로젝트에서 나가시겠습니까?',
|
||||||
areYouSureYouWantToRemoveThisManagerFromProject: '프로젝트에서 관리자를 제거하시겠습니까?',
|
areYouSureYouWantToRemoveThisManagerFromProject:
|
||||||
areYouSureYouWantToRemoveThisMemberFromBoard: '이 회원을 보드에서 탈퇴시키겠습니까?',
|
'해당하는 프로젝트에서 매니저를 제거하시겠습니까?',
|
||||||
attachment: '첨부파일',
|
areYouSureYouWantToRemoveThisMemberFromBoard: '해당하는 멤버를 보드에서 삭제하시겠습니까?',
|
||||||
|
attachment: '첨부',
|
||||||
attachments: '첨부 파일',
|
attachments: '첨부 파일',
|
||||||
authentication: '계정 관리',
|
authentication: '인증',
|
||||||
background: 'Background',
|
background: '배경',
|
||||||
board: '보드',
|
board: '보드',
|
||||||
boardNotFound_title: '보드를 찾을 수 없습니다.',
|
boardNotFound_title: '보드를 찾을 수 없습니다',
|
||||||
cardActions_title: 'Card Actions',
|
canComment: '댓글을 작성할 수 있습니다',
|
||||||
cardNotFound_title: '항목카드를 찾을 수 없습니다.',
|
canEditContentOfBoard: '보드 콘텐츠를 수정할 수 있습니다.',
|
||||||
cardOrActionAreDeleted: '항목카드 혹은 활동내역이 삭제되었습니다.',
|
canOnlyViewBoard: '보드를 읽기만 할 수 있습니다.',
|
||||||
color: 'Color',
|
cardActions_title: '카드 작업',
|
||||||
createBoard_title: '보드명칭을 입력하세요.',
|
cardNotFound_title: '카드를 찾을 수 없습니다',
|
||||||
|
cardOrActionAreDeleted: '카드 또는 작업이 삭제되었습니다.',
|
||||||
|
color: '색상',
|
||||||
|
createBoard_title: '보드 생성',
|
||||||
createLabel_title: '라벨 생성',
|
createLabel_title: '라벨 생성',
|
||||||
createNewOneOrSelectExistingOne: '새 항목을 만들거나<br />기존 항목을 선택하세요.',
|
createNewOneOrSelectExistingOne: '새로 생성하거나<br />기존 항목을 선택하세요.',
|
||||||
createProject_title: '프로젝트 생성',
|
createProject_title: '프로젝트 생성',
|
||||||
createTextFile_title: 'Text File 생성',
|
createTextFile_title: '텍스트 파일 생성',
|
||||||
currentPassword: '현재 비밀번호',
|
currentPassword: '현재 비밀번호',
|
||||||
dangerZone_title: 'Danger Zone',
|
dangerZone_title: '위험 구역',
|
||||||
date: '날짜',
|
date: '날짜',
|
||||||
dueDate_title: '완료일',
|
dueDate_title: '마감일',
|
||||||
deleteAttachment_title: '첨부 파일 삭제',
|
deleteAttachment_title: '첨부 파일 삭제',
|
||||||
deleteBoard_title: '보드 삭제',
|
deleteBoard_title: '보드 삭제',
|
||||||
deleteCard_title: '항목 카드 삭제',
|
deleteCard_title: '카드 삭제',
|
||||||
deleteComment_title: '댓글 삭제',
|
deleteComment_title: '댓글 삭제',
|
||||||
deleteLabel_title: '라벨 삭제',
|
deleteLabel_title: '라벨 삭제',
|
||||||
deleteList_title: '리스트 삭제',
|
deleteList_title: '목록 삭제',
|
||||||
deleteProject_title: '프로젝트 삭제',
|
deleteProject_title: '프로젝트 삭제',
|
||||||
deleteTask_title: '태스크 삭제',
|
deleteTask_title: '업무 삭제',
|
||||||
deleteUser_title: '사용자 삭제',
|
deleteUser_title: '사용자 삭제',
|
||||||
description: '설명',
|
description: '설명',
|
||||||
detectAutomatically: '자동으로 감지',
|
detectAutomatically: '자동으로 감지',
|
||||||
dropFileToUpload: '업로드 하기 위해서 파일을 Drop 하세요.',
|
dropFileToUpload: '업로드할 파일 놓기',
|
||||||
|
editor: '편집기',
|
||||||
editAttachment_title: '첨부 수정',
|
editAttachment_title: '첨부 수정',
|
||||||
editAvatar_title: '프로필사진 수정',
|
editAvatar_title: '아바타 수정',
|
||||||
editBoard_title: '보드 수정',
|
editBoard_title: '보드 수정',
|
||||||
editDueDate_title: '완료일 수정',
|
editDueDate_title: '마감일 수정',
|
||||||
editEmail_title: '메일 수정',
|
editEmail_title: '이메일 수정',
|
||||||
editInformation_title: '정보 수정',
|
editInformation_title: '정보 수정',
|
||||||
editLabel_title: '라벨 수정',
|
editLabel_title: '라벨 수정',
|
||||||
editPassword_title: '비밀번호 수정',
|
editPassword_title: '비밀번호 수정',
|
||||||
editTimer_title: '타이머 수정',
|
editPermissions_title: '권한 수정',
|
||||||
|
editStopwatch_title: '스톱워치 수정',
|
||||||
editUsername_title: '사용자 이름 수정',
|
editUsername_title: '사용자 이름 수정',
|
||||||
email: '메일',
|
email: '이메일',
|
||||||
emailAlreadyInUse: '기 가입된 이메일이 존재합니다.',
|
emailAlreadyInUse: '이미 사용 중인 이메일입니다',
|
||||||
enterCardTitle: '항목카드명을 입력하세요...',
|
enterCardTitle: '카드 제목 입력... [Ctrl+Enter]로 자동으로 열 수 있습니다.',
|
||||||
enterDescription: '자세한 설명을 작성하세요...',
|
enterDescription: '설명 입력...',
|
||||||
enterFilename: '파일이름을 입력하세요...',
|
enterFilename: '파일 이름 입력',
|
||||||
enterListTitle: '리스트명을 입력 (예,todo,완료,이슈등...)',
|
enterListTitle: '목록 제목 입력...',
|
||||||
enterProjectTitle: '프로젝트명칭을 입력하세요.',
|
enterProjectTitle: '프로젝트 제목 입력',
|
||||||
enterTaskDescription: '태스크 설명을 입력하세요...',
|
enterTaskDescription: '업무 설명 입력...',
|
||||||
filterByLabels_title: '라벨 검색',
|
filterByLabels_title: '라벨로 필터링',
|
||||||
filterByMembers_title: '회원으로 검색',
|
filterByMembers_title: '멤버로 필터링',
|
||||||
fromComputer_title: 'PC로 부터',
|
fromComputer_title: 'PC에서',
|
||||||
general: 'General',
|
fromTrello: '트렐로에서',
|
||||||
|
general: '일반',
|
||||||
hours: '시',
|
hours: '시',
|
||||||
invalidCurrentPassword: '현재 패스워드는 유효하지 않습니다.',
|
importBoard_title: '보드 가져오기',
|
||||||
|
invalidCurrentPassword: '현재 비밀번호가 일치하지 않습니다',
|
||||||
labels: '라벨',
|
labels: '라벨',
|
||||||
language: '언어 선택',
|
language: '언어',
|
||||||
leaveBoard_title: '보드 탈퇴',
|
leaveBoard_title: '보드 떠나기',
|
||||||
leaveProject_title: '프로젝트 탈퇴',
|
leaveProject_title: '프로젝트 떠나기',
|
||||||
list: '내역',
|
list: '목록',
|
||||||
listActions_title: '활용 내역',
|
listActions_title: '목록 항목 조율',
|
||||||
managers: '관리자',
|
managers: '매니저',
|
||||||
members: '팀원',
|
members: '멤버',
|
||||||
minutes: '분',
|
minutes: '분',
|
||||||
moveCard_title: '항목카드 이동',
|
moveCard_title: '카드 옮기기',
|
||||||
name: '이름',
|
name: '이름',
|
||||||
newEmail: '새로운 메일',
|
newEmail: '새로운 이메일',
|
||||||
newPassword: '새로운 패스워드',
|
newPassword: '새로운 비밀번호',
|
||||||
newUsername: '새로운 사용자 이름',
|
newUsername: '새로운 사용자 이름',
|
||||||
noConnectionToServer: '서버에 연결할수 없습니다.',
|
noConnectionToServer: '서버에 연결할 수 없습니다',
|
||||||
noBoards: '보드가 존재하지 않습니다.',
|
noBoards: '보드가 존재하지 않습니다',
|
||||||
noLists: '리스트가 존재하지 않습니다.',
|
noLists: '목록이 존재하지 않습니다',
|
||||||
noProjects: '프로젝트가 존재하지 않습니다.',
|
noProjects: '프로젝트가 존재하지 않습니다',
|
||||||
notifications: '알림',
|
notifications: '알림',
|
||||||
noUnreadNotifications: '알림을 모두 확인했습니다.',
|
noUnreadNotifications: '확인하지 않은 알림이 없습니다.',
|
||||||
openBoard_title: '보드 열기',
|
openBoard_title: '보드 열기',
|
||||||
optional_inline: '옵션',
|
optional_inline: '옵션',
|
||||||
organization: '조직',
|
organization: '조직',
|
||||||
phone: '전화번호',
|
phone: '전화번호',
|
||||||
preferences: '환경설정',
|
preferences: '환경설정',
|
||||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||||
'Tip: 클립보드에서 첨부 파일을 추가하려면 Ctrl-V(Mac의 경우 Cmd-V) 입니다..',
|
'팁: 클립보드에서 첨부 파일을 추가하려면 Ctrl-V (Mac의 경우 Cmd-V)를 누릅니다.',
|
||||||
project: '프로젝트',
|
project: '프로젝트',
|
||||||
projectNotFound_title: '프로젝트가 존재하지 않습니다.',
|
projectNotFound_title: '프로젝트를 찾을 수 없습니다',
|
||||||
removeManager_title: '관리자 삭제',
|
removeManager_title: '매니저 삭제',
|
||||||
removeMember_title: '맴버 삭제',
|
removeMember_title: '멤버 삭제',
|
||||||
|
searchLabels: '라벨 검색...',
|
||||||
|
searchMembers: '멤버 검색...',
|
||||||
|
searchUsers: '사용자 검색...',
|
||||||
seconds: '초',
|
seconds: '초',
|
||||||
selectBoard: '보드 선택',
|
selectBoard: '보드 선택',
|
||||||
selectList: '리스트 선택',
|
selectList: '목록 선택',
|
||||||
|
selectPermissions_title: '권한 선택',
|
||||||
selectProject: '프로젝트 선택',
|
selectProject: '프로젝트 선택',
|
||||||
settings: '설정',
|
settings: '설정',
|
||||||
subscribeToMyOwnCardsByDefault: '내 항목카드는 기본으로 구독',
|
stopwatch: '스톱워치',
|
||||||
taskActions_title: '태스크 활동',
|
subscribeToMyOwnCardsByDefault: '기본적으로 내 카드를 구독합니다',
|
||||||
tasks: '태스크',
|
taskActions_title: '업무 조율',
|
||||||
thereIsNoPreviewAvailableForThisAttachment:
|
tasks: '업무',
|
||||||
'이 첨부 파일에 사용할 수 있는 미리보기가 없습니다.',
|
thereIsNoPreviewAvailableForThisAttachment: '이 첨부 파일은 미리 보기를 사용할 수 없습니다.',
|
||||||
time: '시간',
|
time: '시간',
|
||||||
timer: '활동시간',
|
|
||||||
title: '제목',
|
title: '제목',
|
||||||
userActions_title: '',
|
userActions_title: '사용자 조율',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1>이 카드를 {{list}}</1>에 추가했습니다.',
|
userAddedThisCardToList: '<0>{{user}}</0>님이 <1>{{list}}에 카드를 추가했습니다</1>',
|
||||||
userLeftNewCommentToCard:
|
userLeftNewCommentToCard:
|
||||||
'{{user}}이(가) <2>{{card}}</2>에 «{{comment}}»라는 댓글을 남겼습니다',
|
'{{user}}님이 <2>{{card}}</2>에 «{{comment}}»라는 댓글을 남겼습니다',
|
||||||
userMovedCardFromListToList: '{{user}} moved <2>{{card}}</2> from {{fromList}} to {{toList}}',
|
userMovedCardFromListToList:
|
||||||
|
'{{user}}님이 {{fromList}}에서 {{toList}}로 <2>{{card}}</2>를 옮겼습니다',
|
||||||
userMovedThisCardFromListToList:
|
userMovedThisCardFromListToList:
|
||||||
'<0>{{user}}</0><1> 이 항목카드를 {{fromList}} 에서 {{toList}} 으로 이동했습니다. </1>',
|
'<0>{{user}}</0><1> 님이 {{fromList}}에서 {{toList}}로 이 카드를 옮겼습니다</1>',
|
||||||
username: '아이디',
|
username: '사용자 이름',
|
||||||
usernameAlreadyInUse: '이미 사용중인 사용자입니다.',
|
usernameAlreadyInUse: '이미 사용중인 사용자 이름입니다',
|
||||||
users: '사용자',
|
users: '사용자',
|
||||||
writeComment: '댓글 작성...',
|
version: '버전',
|
||||||
|
viewer: '열람자',
|
||||||
|
writeComment: '댓글 쓰기...',
|
||||||
},
|
},
|
||||||
|
|
||||||
action: {
|
action: {
|
||||||
addAnotherCard: '다른 항목카드 추가',
|
addAnotherCard: '다른 카드 추가',
|
||||||
addAnotherList: '다른 리스트 추가',
|
addAnotherList: '다른 목록 추가',
|
||||||
addAnotherTask: '다른 TASK 추가',
|
addAnotherTask: '다른 업무 추가',
|
||||||
addCard: '항목카드 추가',
|
addCard: '카드 추가',
|
||||||
addCard_title: '항목카드 추가',
|
addCard_title: '카드 추가',
|
||||||
addComment: '댓글 추가',
|
addComment: '댓글 추가',
|
||||||
addList: '리스트 추가',
|
addList: '목록 추가',
|
||||||
|
addMember: '멤버 추가',
|
||||||
addMoreDetailedDescription: '자세한 설명 추가',
|
addMoreDetailedDescription: '자세한 설명 추가',
|
||||||
addTask: '태스트 추가',
|
addTask: '업무 추가',
|
||||||
addToCard: '항목카드 추가',
|
addToCard: '카드에 추가',
|
||||||
addUser: '사용자 추가',
|
addUser: '사용자 추가',
|
||||||
createBoard: '보드 생성',
|
createBoard: '보드 생성',
|
||||||
createFile: '파일 생성',
|
createFile: '파일 생성',
|
||||||
@@ -170,50 +186,54 @@ export default {
|
|||||||
createProject: '프로젝트 생성',
|
createProject: '프로젝트 생성',
|
||||||
delete: '삭제',
|
delete: '삭제',
|
||||||
deleteAttachment: '첨부 삭제',
|
deleteAttachment: '첨부 삭제',
|
||||||
deleteAvatar: '프로필 사진 삭제',
|
deleteAvatar: '아바타 삭제',
|
||||||
deleteBoard: '보드 삭제',
|
deleteBoard: '보드 삭제',
|
||||||
deleteCard: '항목카드 삭제',
|
deleteCard: '카드 삭제',
|
||||||
deleteCard_title: '항목카드 삭제',
|
deleteCard_title: '카드 삭제',
|
||||||
deleteComment: '댓글 삭제',
|
deleteComment: '댓글 삭제',
|
||||||
deleteImage: '이미지 삭제',
|
deleteImage: '이미지 삭제',
|
||||||
deleteLabel: '라벨 삭제',
|
deleteLabel: '라벨 삭제',
|
||||||
deleteList: '리스트 삭제',
|
deleteList: '목록 삭제',
|
||||||
deleteList_title: '리스트 삭제',
|
deleteList_title: '목록 삭제',
|
||||||
deleteProject: '프로젝트 삭제',
|
deleteProject: '프로젝트 삭제',
|
||||||
deleteProject_title: '프로젝트 삭제',
|
deleteProject_title: '프로젝트 삭제',
|
||||||
deleteTask: '태스트 삭제',
|
deleteTask: '업무 삭제',
|
||||||
deleteTask_title: '태스트 삭제',
|
deleteTask_title: '업무 삭제',
|
||||||
deleteUser: '사용자 삭제',
|
deleteUser: '사용자 삭제',
|
||||||
edit: '수정',
|
edit: '수정',
|
||||||
editDueDate_title: '완료일 수정',
|
editDueDate_title: '마감일 수정',
|
||||||
editDescription_title: '설명 수정',
|
editDescription_title: '설명 수정',
|
||||||
editEmail_title: '메일 수정',
|
editEmail_title: '이메일 수정',
|
||||||
editInformation_title: '정보 수정',
|
editInformation_title: '정보 수정',
|
||||||
editPassword_title: '패스워드 수정',
|
editPassword_title: '비밀번호 수정',
|
||||||
editTimer_title: '시간 수정',
|
editPermissions: '권한 수정',
|
||||||
|
editStopwatch_title: '스톱워치 수정',
|
||||||
editTitle_title: '제목 수정',
|
editTitle_title: '제목 수정',
|
||||||
editUsername_title: '이름 수정',
|
editUsername_title: '사용자 이름 수정',
|
||||||
leaveBoard: '보드에서 나가기',
|
hideDetails: '세부 사항 숨기기',
|
||||||
leaveProject: '프로젝트에서 나가기',
|
import: '가져오기',
|
||||||
|
leaveBoard: '보드 나가기',
|
||||||
|
leaveProject: '프로젝트 나가기',
|
||||||
logOut_title: '로그아웃',
|
logOut_title: '로그아웃',
|
||||||
makeCover_title: '커버 만들기',
|
makeCover_title: '커버 만들기',
|
||||||
move: '이동',
|
move: '이동',
|
||||||
moveCard_title: '항목카드 이동',
|
moveCard_title: '카드 이동',
|
||||||
remove: '제거',
|
remove: '제거',
|
||||||
removeBackground: '백그라운드 제거',
|
removeBackground: '배경 제거',
|
||||||
removeCover_title: '커버 제거',
|
removeCover_title: '커버 제거',
|
||||||
removeFromBoard: '보드에서 제거',
|
removeFromBoard: '보드 제거',
|
||||||
removeFromProject: '프로젝트에서 제거',
|
removeFromProject: '프로젝트 제거',
|
||||||
removeManager: '관리자 삭제',
|
removeManager: '매니저 제거',
|
||||||
removeMember: '멤버 삭제',
|
removeMember: '멤버 제거',
|
||||||
save: '저장',
|
save: '저장',
|
||||||
showAllAttachments: '모든 첨부파일 표시 ({{hidden}} hidden)',
|
showAllAttachments: '모든 첨부 파일 표시 ({{hidden}} 숨겨짐)',
|
||||||
|
showDetails: '세부 사항 표시',
|
||||||
showFewerAttachments: '일부 첨부 파일만 표시',
|
showFewerAttachments: '일부 첨부 파일만 표시',
|
||||||
start: '시작',
|
start: '시작',
|
||||||
stop: '끝',
|
stop: '종료',
|
||||||
subscribe: '구독하기',
|
subscribe: '구독',
|
||||||
unsubscribe: '구독 취소',
|
unsubscribe: '구독 취소',
|
||||||
uploadNewAvatar: '새로운 프로필 사진 업로드',
|
uploadNewAvatar: '새로운 아바타 업로드',
|
||||||
uploadNewImage: '새로운 이미지 업로드',
|
uploadNewImage: '새로운 이미지 업로드',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
/* Translated by Park Seok Ho */
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
emailOrUsername: '이메일 혹은 아이디',
|
emailOrUsername: '이메일 혹은 사용자 이름',
|
||||||
invalidEmailOrUsername: '이메일이나 사용자 이름이 유효하지 않습니다.',
|
invalidEmailOrUsername: '이메일 혹은 사용자 이름이 유효하지 않습니다',
|
||||||
invalidPassword: '잘못된 비밀번호',
|
invalidPassword: '유효하지 않은 비밀번호',
|
||||||
logInToPlanka: 'Planka에 로그인',
|
logInToPlanka: 'Planka에 로그인',
|
||||||
noInternetConnection: '인터넷에 연결되지 않음',
|
noInternetConnection: '인터넷 연결이 없음',
|
||||||
pageNotFound_title: '페이지를 찾을수 없습니다.',
|
pageNotFound_title: '페이지를 찾을 수 없습니다',
|
||||||
password: '패스워드',
|
password: '비밀번호',
|
||||||
projectManagement: '프로젝트 관리',
|
projectManagement: '프로젝트 관리',
|
||||||
serverConnectionFailed: '서버 연결 실패',
|
serverConnectionFailed: '서버 연결에 실패함',
|
||||||
unknownError: '알 수 없는 오류가 발생하였습니다, 나중에 다시 시도하세요.',
|
unknownError: '알 수 없는 오류, 나중에 다시 시도하십시오',
|
||||||
|
useSingleSignOn: 'single sign-on(SSO) 사용',
|
||||||
},
|
},
|
||||||
|
|
||||||
action: {
|
action: {
|
||||||
logIn: '로그인',
|
logIn: '로그인',
|
||||||
|
logInWithSSO: 'single sign-on(SSO)으로 로그인',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export default {
|
|||||||
dateTime: '$t(format:date) $t(format:time)',
|
dateTime: '$t(format:date) $t(format:time)',
|
||||||
longDate: 'd MMM',
|
longDate: 'd MMM',
|
||||||
longDateTime: "d MMMM 'o' p",
|
longDateTime: "d MMMM 'o' p",
|
||||||
|
fullDate: 'd MMM y',
|
||||||
|
fullDateTime: "d MMMM y 'o' p",
|
||||||
},
|
},
|
||||||
|
|
||||||
translation: {
|
translation: {
|
||||||
@@ -22,7 +24,7 @@ export default {
|
|||||||
administrator: 'Administrator',
|
administrator: 'Administrator',
|
||||||
all: 'Wszyscy',
|
all: 'Wszyscy',
|
||||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||||
'Wszystkie zmiany zostaną automatycznie zapisane<br />po przywróceniu połączenia',
|
'Wszystkie zmiany zostaną automatycznie zapisane<br />po przywróceniu połączenia.',
|
||||||
areYouSureYouWantToDeleteThisAttachment: 'Jesteś pewien że chcesz usunąć ten załącznik?',
|
areYouSureYouWantToDeleteThisAttachment: 'Jesteś pewien że chcesz usunąć ten załącznik?',
|
||||||
areYouSureYouWantToDeleteThisBoard: 'Jesteś pewien że chcesz usunąć tę tablicę?',
|
areYouSureYouWantToDeleteThisBoard: 'Jesteś pewien że chcesz usunąć tę tablicę?',
|
||||||
areYouSureYouWantToDeleteThisCard: 'Jesteś pewien że chcesz usunąć tę kartę?',
|
areYouSureYouWantToDeleteThisCard: 'Jesteś pewien że chcesz usunąć tę kartę?',
|
||||||
@@ -41,11 +43,11 @@ export default {
|
|||||||
boardNotFound_title: 'Tablica Nie Znaleziona',
|
boardNotFound_title: 'Tablica Nie Znaleziona',
|
||||||
cardActions_title: 'Akcje Karty',
|
cardActions_title: 'Akcje Karty',
|
||||||
cardNotFound_title: 'Karta Nie Znaleziona',
|
cardNotFound_title: 'Karta Nie Znaleziona',
|
||||||
cardOrActionAreDeleted: 'Karta lub akcja została usunięta',
|
cardOrActionAreDeleted: 'Karta lub akcja została usunięta.',
|
||||||
color: 'Kolor',
|
color: 'Kolor',
|
||||||
createBoard_title: 'Utwórz Tablicę',
|
createBoard_title: 'Utwórz Tablicę',
|
||||||
createLabel_title: 'Utwórz Oznaczenie',
|
createLabel_title: 'Utwórz Oznaczenie',
|
||||||
createNewOneOrSelectExistingOne: 'Utwórz nowy lub wybierz<br />istniejący',
|
createNewOneOrSelectExistingOne: 'Utwórz nowy lub wybierz<br />istniejący.',
|
||||||
createProject_title: 'Utwórz Projekt',
|
createProject_title: 'Utwórz Projekt',
|
||||||
createTextFile_title: 'Utwórz Plik Tekstowy',
|
createTextFile_title: 'Utwórz Plik Tekstowy',
|
||||||
currentPassword: 'Aktualne hasło',
|
currentPassword: 'Aktualne hasło',
|
||||||
@@ -69,7 +71,7 @@ export default {
|
|||||||
editEmail_title: 'Edytuj E-mail',
|
editEmail_title: 'Edytuj E-mail',
|
||||||
editLabel_title: 'Edytuj Oznaczenie',
|
editLabel_title: 'Edytuj Oznaczenie',
|
||||||
editPassword_title: 'Edytuj Hasło',
|
editPassword_title: 'Edytuj Hasło',
|
||||||
editTimer_title: 'Edytuj Odliczanie',
|
editStopwatch_title: 'Edytuj Odliczanie',
|
||||||
editUsername_title: 'Edytuj Nazwę Użytkownika',
|
editUsername_title: 'Edytuj Nazwę Użytkownika',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail jest już używany',
|
emailAlreadyInUse: 'E-mail jest już używany',
|
||||||
@@ -99,14 +101,14 @@ export default {
|
|||||||
noLists: 'Brak list',
|
noLists: 'Brak list',
|
||||||
noProjects: 'Brak projektów',
|
noProjects: 'Brak projektów',
|
||||||
notifications: 'Powiadomienia',
|
notifications: 'Powiadomienia',
|
||||||
noUnreadNotifications: 'Brak nieprzeczytanych powiadomień',
|
noUnreadNotifications: 'Brak nieprzeczytanych powiadomień.',
|
||||||
openBoard_title: 'Otwórz Tablicę',
|
openBoard_title: 'Otwórz Tablicę',
|
||||||
optional_inline: 'opcjonalny',
|
optional_inline: 'opcjonalny',
|
||||||
organization: 'Organizacja',
|
organization: 'Organizacja',
|
||||||
phone: 'Telefon',
|
phone: 'Telefon',
|
||||||
preferences: 'Preferencje',
|
preferences: 'Preferencje',
|
||||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||||
'Podpowiedź: naciśnij Ctrl-V (Cmd-V na Macu) aby dodać załącznik ze schowka',
|
'Podpowiedź: naciśnij Ctrl-V (Cmd-V na Macu) aby dodać załącznik ze schowka.',
|
||||||
project: 'Projekt',
|
project: 'Projekt',
|
||||||
projectNotFound_title: 'Projekt Nie Znaleziony',
|
projectNotFound_title: 'Projekt Nie Znaleziony',
|
||||||
removeMember_title: 'Usuń Członka',
|
removeMember_title: 'Usuń Członka',
|
||||||
@@ -115,11 +117,11 @@ export default {
|
|||||||
selectList: 'Wybierz listę',
|
selectList: 'Wybierz listę',
|
||||||
selectProject: 'Wybierz projekt',
|
selectProject: 'Wybierz projekt',
|
||||||
settings: 'Ustawienia',
|
settings: 'Ustawienia',
|
||||||
|
stopwatch: 'Odliczanie',
|
||||||
subscribeToMyOwnCardsByDefault: 'Zapisz mnie do moich kart automatycznie',
|
subscribeToMyOwnCardsByDefault: 'Zapisz mnie do moich kart automatycznie',
|
||||||
taskActions_title: 'Akcje Zadania',
|
taskActions_title: 'Akcje Zadania',
|
||||||
tasks: 'Zadania',
|
tasks: 'Zadania',
|
||||||
time: 'Czas',
|
time: 'Czas',
|
||||||
timer: 'Odliczanie',
|
|
||||||
title: 'Tytuł',
|
title: 'Tytuł',
|
||||||
userActions_title: 'Akcje użytkownika',
|
userActions_title: 'Akcje użytkownika',
|
||||||
userAddedThisCardToList: '<0>{{user}}</0><1> dodał tę kartę w {{list}}</1>',
|
userAddedThisCardToList: '<0>{{user}}</0><1> dodał tę kartę w {{list}}</1>',
|
||||||
@@ -172,7 +174,7 @@ export default {
|
|||||||
editDescription_title: 'Edytuj Opis',
|
editDescription_title: 'Edytuj Opis',
|
||||||
editEmail_title: 'Edytuj E-mail',
|
editEmail_title: 'Edytuj E-mail',
|
||||||
editPassword_title: 'Edytuj Hasło',
|
editPassword_title: 'Edytuj Hasło',
|
||||||
editTimer_title: 'Edytuj Odliczanie',
|
editStopwatch_title: 'Edytuj Odliczanie',
|
||||||
editTitle_title: 'Edytuj Tytuł',
|
editTitle_title: 'Edytuj Tytuł',
|
||||||
editUsername_title: 'Edytuj Nazwę Użytkownika',
|
editUsername_title: 'Edytuj Nazwę Użytkownika',
|
||||||
logOut_title: 'Wyloguj',
|
logOut_title: 'Wyloguj',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user