mirror of
https://github.com/mattermost/focalboard.git
synced 2025-01-02 14:47:55 +02:00
Merge branch 'main' into GH-4476-No-card-delete-confirmation-status
This commit is contained in:
commit
ef31f87ace
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@ -15,7 +15,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
ci-ubuntu-server:
|
ci-ubuntu-server:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@ -44,7 +44,7 @@ jobs:
|
|||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
@ -54,7 +54,7 @@ jobs:
|
|||||||
run: cd focalboard; make server-test-${{matrix['db']}}
|
run: cd focalboard; make server-test-${{matrix['db']}}
|
||||||
|
|
||||||
ci-ubuntu-webapp:
|
ci-ubuntu-webapp:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@ -74,7 +74,7 @@ jobs:
|
|||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
- name: npm ci
|
- name: npm ci
|
||||||
run: |
|
run: |
|
||||||
cd focalboard/webapp && npm ci && cd -
|
cd focalboard/webapp && npm ci && cd -
|
||||||
@ -132,7 +132,7 @@ jobs:
|
|||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
@ -169,7 +169,7 @@ jobs:
|
|||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
|
31
.github/workflows/dev-release.yml
vendored
31
.github/workflows/dev-release.yml
vendored
@ -8,14 +8,13 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||||
EXCLUDE_ENTERPRISE: true
|
EXCLUDE_ENTERPRISE: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
ubuntu:
|
ubuntu:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@ -25,16 +24,16 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref: ${{ env.BRANCH_NAME }}
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
if: steps.mattermostServer.outcome == 'failure'
|
if: steps.mattermostServer.outcome == 'failure'
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
|
|
||||||
- name: Replace token 1 server
|
- name: Replace token 1 server
|
||||||
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
||||||
@ -101,16 +100,16 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref: ${{ env.BRANCH_NAME }}
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
if: steps.mattermostServer.outcome == 'failure'
|
if: steps.mattermostServer.outcome == 'failure'
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
- name: Replace token 1 server
|
- name: Replace token 1 server
|
||||||
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
||||||
|
|
||||||
@ -159,16 +158,16 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref: ${{ env.BRANCH_NAME }}
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
if: steps.mattermostServer.outcome == 'failure'
|
if: steps.mattermostServer.outcome == 'failure'
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
- name: Replace token 1 server
|
- name: Replace token 1 server
|
||||||
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
||||||
|
|
||||||
@ -218,7 +217,7 @@ jobs:
|
|||||||
path: ${{ github.workspace }}/focalboard/win-wpf/dist/focalboard-win.zip
|
path: ${{ github.workspace }}/focalboard/win-wpf/dist/focalboard-win.zip
|
||||||
|
|
||||||
plugin:
|
plugin:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@ -229,16 +228,16 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref: ${{ env.BRANCH_NAME }}
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
if: steps.mattermostServer.outcome == 'failure'
|
if: steps.mattermostServer.outcome == 'failure'
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
|
|
||||||
- name: Replace token 1 server
|
- name: Replace token 1 server
|
||||||
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
||||||
|
6
.github/workflows/lint-server.yml
vendored
6
.github/workflows/lint-server.yml
vendored
@ -13,7 +13,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
down-migrations:
|
down-migrations:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@ -26,7 +26,7 @@ jobs:
|
|||||||
|
|
||||||
golangci:
|
golangci:
|
||||||
name: plugin
|
name: plugin
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
@ -48,7 +48,7 @@ jobs:
|
|||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
- name: set up golangci-lint
|
- name: set up golangci-lint
|
||||||
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.1
|
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.1
|
||||||
- name: lint
|
- name: lint
|
||||||
|
28
.github/workflows/prod-release.yml
vendored
28
.github/workflows/prod-release.yml
vendored
@ -9,7 +9,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
ubuntu:
|
ubuntu:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@ -21,16 +21,16 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref: ${{ env.BRANCH_NAME }}
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
if: steps.mattermostServer.outcome == 'failure'
|
if: steps.mattermostServer.outcome == 'failure'
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
|
|
||||||
- name: Replace token 1 server
|
- name: Replace token 1 server
|
||||||
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
||||||
@ -97,16 +97,16 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref: ${{ env.BRANCH_NAME }}
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
if: steps.mattermostServer.outcome == 'failure'
|
if: steps.mattermostServer.outcome == 'failure'
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
|
|
||||||
- name: Replace token 1 server
|
- name: Replace token 1 server
|
||||||
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
||||||
@ -156,16 +156,16 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref: ${{ env.BRANCH_NAME }}
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
if: steps.mattermostServer.outcome == 'failure'
|
if: steps.mattermostServer.outcome == 'failure'
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
|
|
||||||
- name: Replace token 1 server
|
- name: Replace token 1 server
|
||||||
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
||||||
@ -216,7 +216,7 @@ jobs:
|
|||||||
path: ${{ github.workspace }}/focalboard/win-wpf/dist/focalboard-win.zip
|
path: ${{ github.workspace }}/focalboard/win-wpf/dist/focalboard-win.zip
|
||||||
|
|
||||||
plugin-release:
|
plugin-release:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@ -228,16 +228,16 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref: ${{ env.BRANCH_NAME }}
|
ref: ${{ env.BRANCH_NAME }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
if: steps.mattermostServer.outcome == 'failure'
|
if: steps.mattermostServer.outcome == 'failure'
|
||||||
with:
|
with:
|
||||||
repository: "mattermost/mattermost-server"
|
repository: "mattermost/mattermost-server"
|
||||||
fetch-depth: "20"
|
fetch-depth: "20"
|
||||||
path: "mattermost-server"
|
path: "mattermost-server"
|
||||||
ref : "master"
|
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||||
|
|
||||||
- name: Replace token 1 server
|
- name: Replace token 1 server
|
||||||
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
run: sed -i -e "s,placeholder_rudder_dataplane_url,${{ secrets.RUDDER_DATAPLANE_URL }},g" ${{ github.workspace }}/focalboard/server/services/telemetry/telemetry.go
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# This dockerfile is used to build Focalboard for Linux
|
# This Dockerfile is used to build Focalboard for Linux. It builds all the parts inside the image
|
||||||
# it builds all the parts inside the container and the last stage just holds the
|
# and the last stage just holds the package which is then copied back to the host.
|
||||||
# package that can be extracted using docker cp command
|
#
|
||||||
# ie
|
# docker buildx build -f Dockerfile.build --no-cache --platform linux/amd64 -t focalboard-build:dirty --output out .
|
||||||
# docker build -f Dockerfile.build --no-cache -t focalboard-build:dirty .
|
# docker buildx build -f Dockerfile.build --no-cache --platform linux/arm64 -t focalboard-build:dirty --output out .
|
||||||
# docker run --rm -v /tmp/dist:/tmp -d --name test focalboard-build:dirty /bin/sh -c 'sleep 1000'
|
#
|
||||||
# docker cp test:/dist/focalboard-server-linux-amd64.tar.gz .
|
# Afterwards the packages can be found in the ./out folder.
|
||||||
|
|
||||||
# build frontend
|
# build frontend
|
||||||
FROM node:16.3.0@sha256:ca6daf1543242acb0ca59ff425509eab7defb9452f6ae07c156893db06c7a9a4 AS frontend
|
FROM node:16.3.0@sha256:ca6daf1543242acb0ca59ff425509eab7defb9452f6ae07c156893db06c7a9a4 AS frontend
|
||||||
@ -12,8 +12,10 @@ FROM node:16.3.0@sha256:ca6daf1543242acb0ca59ff425509eab7defb9452f6ae07c156893db
|
|||||||
WORKDIR /webapp
|
WORKDIR /webapp
|
||||||
COPY webapp .
|
COPY webapp .
|
||||||
|
|
||||||
RUN npm install --no-optional
|
### 'CPPFLAGS="-DPNG_ARM_NEON_OPT=0"' Needed To Avoid Bug Described in: https://github.com/imagemin/optipng-bin/issues/118#issuecomment-1019838562
|
||||||
RUN npm run pack
|
### Can be Removed when Ticket will be Closed
|
||||||
|
RUN CPPFLAGS="-DPNG_ARM_NEON_OPT=0" npm install --no-optional && \
|
||||||
|
npm run pack
|
||||||
|
|
||||||
# build backend and package
|
# build backend and package
|
||||||
FROM golang:1.18.3@sha256:b203dc573d81da7b3176264bfa447bd7c10c9347689be40540381838d75eebef AS backend
|
FROM golang:1.18.3@sha256:b203dc573d81da7b3176264bfa447bd7c10c9347689be40540381838d75eebef AS backend
|
||||||
@ -21,13 +23,13 @@ FROM golang:1.18.3@sha256:b203dc573d81da7b3176264bfa447bd7c10c9347689be405403818
|
|||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=frontend /webapp/pack webapp/pack
|
COPY --from=frontend /webapp/pack webapp/pack
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
# RUN apt-get update && apt-get install libgtk-3-dev libwebkit2gtk-4.0-dev -y
|
# RUN apt-get update && apt-get install libgtk-3-dev libwebkit2gtk-4.0-dev -y
|
||||||
RUN make server-linux
|
RUN EXCLUDE_PLUGIN=true EXCLUDE_SERVER=true EXCLUDE_ENTERPRISE=true make server-linux arch=${TARGETARCH}
|
||||||
RUN make server-linux-package-docker
|
RUN make server-linux-package-docker arch=${TARGETARCH}
|
||||||
|
|
||||||
# just hold the packages to output later
|
# Copy package back to host
|
||||||
FROM alpine:3.12@sha256:c75ac27b49326926b803b9ed43bf088bc220d22556de1bc5f72d742c91398f69 AS dist
|
FROM scratch AS dist
|
||||||
|
ARG TARGETARCH
|
||||||
WORKDIR /dist
|
COPY --from=backend /go/dist/focalboard-server-linux-${TARGETARCH}.tar.gz .
|
||||||
|
|
||||||
COPY --from=backend /go/dist/focalboard-server-linux-amd64.tar.gz .
|
|
||||||
|
4
Makefile
4
Makefile
@ -63,7 +63,7 @@ endif
|
|||||||
server-linux: setup-go-work ## Build server for Linux.
|
server-linux: setup-go-work ## Build server for Linux.
|
||||||
mkdir -p bin/linux
|
mkdir -p bin/linux
|
||||||
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=linux")
|
$(eval LDFLAGS += -X "github.com/mattermost/focalboard/server/model.Edition=linux")
|
||||||
cd server; env GOOS=linux GOARCH=amd64 go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/linux/focalboard-server ./main
|
cd server; env GOOS=linux GOARCH=$(arch) go build -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -o ../bin/linux/focalboard-server ./main
|
||||||
|
|
||||||
server-docker: setup-go-work ## Build server for Docker Architectures.
|
server-docker: setup-go-work ## Build server for Docker Architectures.
|
||||||
mkdir -p bin/docker
|
mkdir -p bin/docker
|
||||||
@ -101,7 +101,7 @@ server-linux-package-docker:
|
|||||||
cp NOTICE.txt package/${PACKAGE_FOLDER}
|
cp NOTICE.txt package/${PACKAGE_FOLDER}
|
||||||
cp webapp/NOTICE.txt package/${PACKAGE_FOLDER}/webapp-NOTICE.txt
|
cp webapp/NOTICE.txt package/${PACKAGE_FOLDER}/webapp-NOTICE.txt
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
cd package && tar -czvf ../dist/focalboard-server-linux-amd64.tar.gz ${PACKAGE_FOLDER}
|
cd package && tar -czvf ../dist/focalboard-server-linux-$(arch).tar.gz ${PACKAGE_FOLDER}
|
||||||
rm -rf package
|
rm -rf package
|
||||||
|
|
||||||
generate: ## Install and run code generators.
|
generate: ## Install and run code generators.
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"support_url": "https://github.com/mattermost/focalboard/issues",
|
"support_url": "https://github.com/mattermost/focalboard/issues",
|
||||||
"release_notes_url": "https://github.com/mattermost/focalboard/releases",
|
"release_notes_url": "https://github.com/mattermost/focalboard/releases",
|
||||||
"icon_path": "assets/starter-template-icon.svg",
|
"icon_path": "assets/starter-template-icon.svg",
|
||||||
"version": "7.9.0",
|
"version": "7.10.0",
|
||||||
"min_server_version": "7.2.0",
|
"min_server_version": "7.2.0",
|
||||||
"server": {
|
"server": {
|
||||||
"executables": {
|
"executables": {
|
||||||
|
5
mattermost-plugin/server/manifest.go
generated
5
mattermost-plugin/server/manifest.go
generated
@ -20,7 +20,7 @@ const manifestStr = `
|
|||||||
"support_url": "https://github.com/mattermost/focalboard/issues",
|
"support_url": "https://github.com/mattermost/focalboard/issues",
|
||||||
"release_notes_url": "https://github.com/mattermost/focalboard/releases",
|
"release_notes_url": "https://github.com/mattermost/focalboard/releases",
|
||||||
"icon_path": "assets/starter-template-icon.svg",
|
"icon_path": "assets/starter-template-icon.svg",
|
||||||
"version": "7.9.0",
|
"version": "7.10.0",
|
||||||
"min_server_version": "7.2.0",
|
"min_server_version": "7.2.0",
|
||||||
"server": {
|
"server": {
|
||||||
"executables": {
|
"executables": {
|
||||||
@ -45,8 +45,7 @@ const manifestStr = `
|
|||||||
"type": "bool",
|
"type": "bool",
|
||||||
"help_text": "This allows board editors to share boards that can be accessed by anyone with the link.",
|
"help_text": "This allows board editors to share boards that can be accessed by anyone with the link.",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"default": false,
|
"default": false
|
||||||
"hosting": ""
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ const CreateBoardFromTemplate = (props: Props) => {
|
|||||||
|
|
||||||
let boardsAndBlocks = undefined
|
let boardsAndBlocks = undefined
|
||||||
|
|
||||||
if (selectedBoardTemplateId === EMPTY_BOARD) {
|
if (templateIdRef.current === EMPTY_BOARD) {
|
||||||
boardsAndBlocks = await mutator.addEmptyBoard(teamId, intl)
|
boardsAndBlocks = await mutator.addEmptyBoard(teamId, intl)
|
||||||
} else {
|
} else {
|
||||||
boardsAndBlocks = await mutator.duplicateBoard(templateIdRef.current as string, ACTION_DESCRIPTION, asTemplate, undefined, undefined, teamId)
|
boardsAndBlocks = await mutator.duplicateBoard(templateIdRef.current as string, ACTION_DESCRIPTION, asTemplate, undefined, undefined, teamId)
|
||||||
|
@ -17,8 +17,10 @@ import CompassIcon from '../../../../webapp/src/widgets/icons/compassIcon'
|
|||||||
|
|
||||||
import {Permission} from '../../../../webapp/src/constants'
|
import {Permission} from '../../../../webapp/src/constants'
|
||||||
|
|
||||||
import './rhsChannelBoardItem.scss'
|
|
||||||
import BoardPermissionGate from '../../../../webapp/src/components/permissions/boardPermissionGate'
|
import BoardPermissionGate from '../../../../webapp/src/components/permissions/boardPermissionGate'
|
||||||
|
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../../../webapp/src/telemetry/telemetryClient'
|
||||||
|
|
||||||
|
import './rhsChannelBoardItem.scss'
|
||||||
|
|
||||||
const windowAny = (window as SuiteWindow)
|
const windowAny = (window as SuiteWindow)
|
||||||
|
|
||||||
@ -36,6 +38,10 @@ const RHSChannelBoardItem = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleBoardClicked = (boardID: string) => {
|
const handleBoardClicked = (boardID: string) => {
|
||||||
|
// send the telemetry information for the clicked board
|
||||||
|
const extraData = {teamID: team.id, board: boardID}
|
||||||
|
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ClickChannelsRHSBoard, extraData)
|
||||||
|
|
||||||
window.open(`${windowAny.frontendBaseURL}/team/${team.id}/${boardID}`, '_blank', 'noopener')
|
window.open(`${windowAny.frontendBaseURL}/team/${team.id}/${boardID}`, '_blank', 'noopener')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ func jsonStringResponse(w http.ResponseWriter, code int, message string) { //nol
|
|||||||
fmt.Fprint(w, message)
|
fmt.Fprint(w, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) {
|
func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) { //nolint:unparam
|
||||||
setResponseHeader(w, "Content-Type", "application/json")
|
setResponseHeader(w, "Content-Type", "application/json")
|
||||||
w.WriteHeader(code)
|
w.WriteHeader(code)
|
||||||
_, _ = w.Write(json)
|
_, _ = w.Write(json)
|
||||||
|
@ -8,6 +8,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -20,9 +22,30 @@ import (
|
|||||||
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/web"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var UnsafeContentTypes = [...]string{
|
||||||
|
"application/javascript",
|
||||||
|
"application/ecmascript",
|
||||||
|
"text/javascript",
|
||||||
|
"text/ecmascript",
|
||||||
|
"application/x-javascript",
|
||||||
|
"text/html",
|
||||||
|
}
|
||||||
|
|
||||||
|
var MediaContentTypes = [...]string{
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/bmp",
|
||||||
|
"image/gif",
|
||||||
|
"image/tiff",
|
||||||
|
"video/avi",
|
||||||
|
"video/mpeg",
|
||||||
|
"video/mp4",
|
||||||
|
"audio/mpeg",
|
||||||
|
"audio/wav",
|
||||||
|
}
|
||||||
|
|
||||||
// FileUploadResponse is the response to a file upload
|
// FileUploadResponse is the response to a file upload
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type FileUploadResponse struct {
|
type FileUploadResponse struct {
|
||||||
@ -123,37 +146,12 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
|
|||||||
auditRec.AddMeta("teamID", board.TeamID)
|
auditRec.AddMeta("teamID", board.TeamID)
|
||||||
auditRec.AddMeta("filename", filename)
|
auditRec.AddMeta("filename", filename)
|
||||||
|
|
||||||
fileInfo, err := a.app.GetFileInfo(filename)
|
fileInfo, fileReader, err := a.app.GetFile(board.TeamID, boardID, filename)
|
||||||
if err != nil && !model.IsErrNotFound(err) {
|
if err != nil && !model.IsErrNotFound(err) {
|
||||||
a.errorResponse(w, r, err)
|
a.errorResponse(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileInfo != nil && fileInfo.Archived {
|
|
||||||
fileMetadata := map[string]interface{}{
|
|
||||||
"archived": true,
|
|
||||||
"name": fileInfo.Name,
|
|
||||||
"size": fileInfo.Size,
|
|
||||||
"extension": fileInfo.Extension,
|
|
||||||
}
|
|
||||||
|
|
||||||
data, jsonErr := json.Marshal(fileMetadata)
|
|
||||||
if jsonErr != nil {
|
|
||||||
a.logger.Error("failed to marshal archived file metadata", mlog.String("filename", filename), mlog.Err(jsonErr))
|
|
||||||
a.errorResponse(w, r, jsonErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonBytesResponse(w, http.StatusBadRequest, data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fileReader, err := a.app.GetFileReader(board.TeamID, boardID, filename)
|
|
||||||
if err != nil && !errors.Is(err, app.ErrFileNotFound) {
|
|
||||||
a.errorResponse(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, app.ErrFileNotFound) && board.ChannelID != "" {
|
if errors.Is(err, app.ErrFileNotFound) && board.ChannelID != "" {
|
||||||
// prior to moving from workspaces to teams, the filepath was constructed from
|
// prior to moving from workspaces to teams, the filepath was constructed from
|
||||||
// workspaceID, which is the channel ID in plugin mode.
|
// workspaceID, which is the channel ID in plugin mode.
|
||||||
@ -170,10 +168,74 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer fileReader.Close()
|
defer fileReader.Close()
|
||||||
web.WriteFileResponse(filename, fileInfo.MimeType, fileInfo.Size, time.Now(), "", fileReader, false, w, r)
|
mimeType := ""
|
||||||
|
var fileSize int64
|
||||||
|
if fileInfo != nil {
|
||||||
|
mimeType = fileInfo.MimeType
|
||||||
|
fileSize = fileInfo.Size
|
||||||
|
}
|
||||||
|
writeFileResponse(filename, mimeType, fileSize, time.Now(), "", fileReader, false, w, r)
|
||||||
auditRec.Success()
|
auditRec.Success()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeFileResponse(filename string, contentType string, contentSize int64,
|
||||||
|
lastModification time.Time, webserverMode string, fileReader io.ReadSeeker, forceDownload bool, w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Cache-Control", "private, no-cache")
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
|
||||||
|
if contentSize > 0 {
|
||||||
|
contentSizeStr := strconv.Itoa(int(contentSize))
|
||||||
|
if webserverMode == "gzip" {
|
||||||
|
w.Header().Set("X-Uncompressed-Content-Length", contentSizeStr)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Content-Length", contentSizeStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = "application/octet-stream"
|
||||||
|
} else {
|
||||||
|
for _, unsafeContentType := range UnsafeContentTypes {
|
||||||
|
if strings.HasPrefix(contentType, unsafeContentType) {
|
||||||
|
contentType = "text/plain"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
|
||||||
|
var toDownload bool
|
||||||
|
if forceDownload {
|
||||||
|
toDownload = true
|
||||||
|
} else {
|
||||||
|
isMediaType := false
|
||||||
|
|
||||||
|
for _, mediaContentType := range MediaContentTypes {
|
||||||
|
if strings.HasPrefix(contentType, mediaContentType) {
|
||||||
|
isMediaType = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toDownload = !isMediaType
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = url.PathEscape(filename)
|
||||||
|
|
||||||
|
if toDownload {
|
||||||
|
w.Header().Set("Content-Disposition", "attachment;filename=\""+filename+"\"; filename*=UTF-8''"+filename)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Content-Disposition", "inline;filename=\""+filename+"\"; filename*=UTF-8''"+filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent file links from being embedded in iframes
|
||||||
|
w.Header().Set("X-Frame-Options", "DENY")
|
||||||
|
w.Header().Set("Content-Security-Policy", "Frame-ancestors 'none'")
|
||||||
|
|
||||||
|
http.ServeContent(w, r, filename, lastModification, fileReader)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *API) getFileInfo(w http.ResponseWriter, r *http.Request) {
|
func (a *API) getFileInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
// swagger:operation GET /files/teams/{teamID}/{boardID}/{filename}/info getFile
|
// swagger:operation GET /files/teams/{teamID}/{boardID}/{filename}/info getFile
|
||||||
//
|
//
|
||||||
|
@ -355,12 +355,15 @@ func (a *App) PatchBoard(patch *model.BoardPatch, boardID, userID string) (*mode
|
|||||||
var oldMembers []*model.BoardMember
|
var oldMembers []*model.BoardMember
|
||||||
|
|
||||||
if patch.Type != nil || patch.ChannelID != nil {
|
if patch.Type != nil || patch.ChannelID != nil {
|
||||||
|
testChannel := ""
|
||||||
if patch.ChannelID != nil && *patch.ChannelID == "" {
|
if patch.ChannelID != nil && *patch.ChannelID == "" {
|
||||||
var err error
|
var err error
|
||||||
oldMembers, err = a.GetMembersForBoard(boardID)
|
oldMembers, err = a.GetMembersForBoard(boardID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Error("Unable to get the board members", mlog.Err(err))
|
a.logger.Error("Unable to get the board members", mlog.Err(err))
|
||||||
}
|
}
|
||||||
|
} else if patch.ChannelID != nil && *patch.ChannelID != "" {
|
||||||
|
testChannel = *patch.ChannelID
|
||||||
}
|
}
|
||||||
|
|
||||||
board, err := a.store.GetBoard(boardID)
|
board, err := a.store.GetBoard(boardID)
|
||||||
@ -372,7 +375,17 @@ func (a *App) PatchBoard(patch *model.BoardPatch, boardID, userID string) (*mode
|
|||||||
}
|
}
|
||||||
oldChannelID = board.ChannelID
|
oldChannelID = board.ChannelID
|
||||||
isTemplate = board.IsTemplate
|
isTemplate = board.IsTemplate
|
||||||
|
if testChannel == "" {
|
||||||
|
testChannel = oldChannelID
|
||||||
|
}
|
||||||
|
|
||||||
|
if testChannel != "" {
|
||||||
|
if !a.permissions.HasPermissionToChannel(userID, testChannel, model.PermissionCreatePost) {
|
||||||
|
return nil, model.NewErrPermission("access denied to channel")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedBoard, err := a.store.PatchBoard(boardID, patch, userID)
|
updatedBoard, err := a.store.PatchBoard(boardID, patch, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -185,6 +185,7 @@ func TestPatchBoard(t *testing.T) {
|
|||||||
|
|
||||||
// Type not null will retrieve team members
|
// Type not null will retrieve team members
|
||||||
th.Store.EXPECT().GetUsersByTeam(teamID, "", false, false).Return([]*model.User{}, nil)
|
th.Store.EXPECT().GetUsersByTeam(teamID, "", false, false).Return([]*model.User{}, nil)
|
||||||
|
th.Store.EXPECT().GetUserByID(userID).Return(&model.User{ID: userID, Username: "UserName"}, nil)
|
||||||
|
|
||||||
th.Store.EXPECT().PatchBoard(boardID, patch, userID).Return(
|
th.Store.EXPECT().PatchBoard(boardID, patch, userID).Return(
|
||||||
&model.Board{
|
&model.Board{
|
||||||
@ -399,6 +400,104 @@ func TestPatchBoard(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, boardID, patchedBoard.ID)
|
require.Equal(t, boardID, patchedBoard.ID)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("patch type channel, user without post permissions", func(t *testing.T) {
|
||||||
|
const boardID = "board_id_1"
|
||||||
|
const userID = "user_id_2"
|
||||||
|
const teamID = "team_id_1"
|
||||||
|
|
||||||
|
channelID := "myChannel"
|
||||||
|
patchType := model.BoardTypeOpen
|
||||||
|
patch := &model.BoardPatch{
|
||||||
|
Type: &patchType,
|
||||||
|
ChannelID: &channelID,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type not nil, will cause board to be reteived
|
||||||
|
// to check isTemplate
|
||||||
|
th.Store.EXPECT().GetBoard(boardID).Return(&model.Board{
|
||||||
|
ID: boardID,
|
||||||
|
TeamID: teamID,
|
||||||
|
IsTemplate: true,
|
||||||
|
}, nil).Times(1)
|
||||||
|
|
||||||
|
th.API.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionCreatePost).Return(false).Times(1)
|
||||||
|
_, err := th.App.PatchBoard(patch, boardID, userID)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("patch type channel, user with post permissions", func(t *testing.T) {
|
||||||
|
const boardID = "board_id_1"
|
||||||
|
const userID = "user_id_2"
|
||||||
|
const teamID = "team_id_1"
|
||||||
|
|
||||||
|
channelID := "myChannel"
|
||||||
|
patch := &model.BoardPatch{
|
||||||
|
ChannelID: &channelID,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type not nil, will cause board to be reteived
|
||||||
|
// to check isTemplate
|
||||||
|
th.Store.EXPECT().GetBoard(boardID).Return(&model.Board{
|
||||||
|
ID: boardID,
|
||||||
|
TeamID: teamID,
|
||||||
|
}, nil).Times(2)
|
||||||
|
|
||||||
|
th.API.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionCreatePost).Return(true).Times(1)
|
||||||
|
|
||||||
|
th.Store.EXPECT().PatchBoard(boardID, patch, userID).Return(
|
||||||
|
&model.Board{
|
||||||
|
ID: boardID,
|
||||||
|
TeamID: teamID,
|
||||||
|
},
|
||||||
|
nil)
|
||||||
|
|
||||||
|
// Should call GetMembersForBoard 2 times
|
||||||
|
// - for WS BroadcastBoardChange
|
||||||
|
// - for AddTeamMembers check
|
||||||
|
th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{}, nil).Times(2)
|
||||||
|
|
||||||
|
th.Store.EXPECT().PostMessage(utils.Anything, "", "").Times(1)
|
||||||
|
|
||||||
|
patchedBoard, err := th.App.PatchBoard(patch, boardID, userID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, boardID, patchedBoard.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("patch type remove channel, user without post permissions", func(t *testing.T) {
|
||||||
|
const boardID = "board_id_1"
|
||||||
|
const userID = "user_id_2"
|
||||||
|
const teamID = "team_id_1"
|
||||||
|
|
||||||
|
const channelID = "myChannel"
|
||||||
|
clearChannel := ""
|
||||||
|
patchType := model.BoardTypeOpen
|
||||||
|
patch := &model.BoardPatch{
|
||||||
|
Type: &patchType,
|
||||||
|
ChannelID: &clearChannel,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type not nil, will cause board to be reteived
|
||||||
|
// to check isTemplate
|
||||||
|
th.Store.EXPECT().GetBoard(boardID).Return(&model.Board{
|
||||||
|
ID: boardID,
|
||||||
|
TeamID: teamID,
|
||||||
|
IsTemplate: true,
|
||||||
|
ChannelID: channelID,
|
||||||
|
}, nil).Times(2)
|
||||||
|
|
||||||
|
th.API.EXPECT().HasPermissionToChannel(userID, channelID, model.PermissionCreatePost).Return(false).Times(1)
|
||||||
|
|
||||||
|
th.API.EXPECT().HasPermissionToTeam(userID, teamID, model.PermissionManageTeam).Return(false).Times(1)
|
||||||
|
// Should call GetMembersForBoard 2 times
|
||||||
|
// for WS BroadcastBoardChange
|
||||||
|
// for AddTeamMembers check
|
||||||
|
// We are returning the user as a direct Board Member, so BroadcastMemberDelete won't be called
|
||||||
|
th.Store.EXPECT().GetMembersForBoard(boardID).Return([]*model.BoardMember{{BoardID: boardID, UserID: userID, SchemeEditor: true}}, nil).Times(1)
|
||||||
|
|
||||||
|
_, err := th.App.PatchBoard(patch, boardID, userID)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetBoardCount(t *testing.T) {
|
func TestGetBoardCount(t *testing.T) {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
@ -28,7 +29,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
|
|||||||
|
|
||||||
createdFilename := utils.NewID(utils.IDTypeNone)
|
createdFilename := utils.NewID(utils.IDTypeNone)
|
||||||
fullFilename := fmt.Sprintf(`%s%s`, createdFilename, fileExtension)
|
fullFilename := fmt.Sprintf(`%s%s`, createdFilename, fileExtension)
|
||||||
filePath := filepath.Join(teamID, rootID, fullFilename)
|
filePath := filepath.Join(utils.GetBaseFilePath(), fullFilename)
|
||||||
|
|
||||||
fileSize, appErr := a.filesBackend.WriteFile(reader, filePath)
|
fileSize, appErr := a.filesBackend.WriteFile(reader, filePath)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
@ -45,7 +46,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
|
|||||||
CreateAt: now,
|
CreateAt: now,
|
||||||
UpdateAt: now,
|
UpdateAt: now,
|
||||||
DeleteAt: 0,
|
DeleteAt: 0,
|
||||||
Path: emptyString,
|
Path: filePath,
|
||||||
ThumbnailPath: emptyString,
|
ThumbnailPath: emptyString,
|
||||||
PreviewPath: emptyString,
|
PreviewPath: emptyString,
|
||||||
Name: filename,
|
Name: filename,
|
||||||
@ -59,6 +60,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
|
|||||||
Content: "",
|
Content: "",
|
||||||
RemoteId: nil,
|
RemoteId: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := a.store.SaveFileInfo(fileInfo)
|
err := a.store.SaveFileInfo(fileInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -77,6 +79,7 @@ func (a *App) GetFileInfo(filename string) (*mmModel.FileInfo, error) {
|
|||||||
// will be the fileinfo id.
|
// will be the fileinfo id.
|
||||||
parts := strings.Split(filename, ".")
|
parts := strings.Split(filename, ".")
|
||||||
fileInfoID := parts[0][1:]
|
fileInfoID := parts[0][1:]
|
||||||
|
|
||||||
fileInfo, err := a.store.GetFileInfo(fileInfoID)
|
fileInfo, err := a.store.GetFileInfo(fileInfoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -85,6 +88,40 @@ func (a *App) GetFileInfo(filename string) (*mmModel.FileInfo, error) {
|
|||||||
return fileInfo, nil
|
return fileInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) GetFile(teamID, rootID, fileName string) (*mmModel.FileInfo, filestore.ReadCloseSeeker, error) {
|
||||||
|
fileInfo, err := a.GetFileInfo(fileName)
|
||||||
|
if err != nil && !model.IsErrNotFound(err) {
|
||||||
|
a.logger.Error("111")
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var filePath string
|
||||||
|
|
||||||
|
if fileInfo != nil && fileInfo.Path != "" {
|
||||||
|
filePath = fileInfo.Path
|
||||||
|
} else {
|
||||||
|
filePath = filepath.Join(teamID, rootID, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := a.filesBackend.FileExists(filePath)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error(fmt.Sprintf("GetFile: Failed to check if file exists as path. Path: %s, error: %e", filePath, err))
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil, nil, ErrFileNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := a.filesBackend.Reader(filePath)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error(fmt.Sprintf("GetFile: Failed to get file reader of existing file at path: %s, error: %e", filePath, err))
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileInfo, reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) GetFileReader(teamID, rootID, filename string) (filestore.ReadCloseSeeker, error) {
|
func (a *App) GetFileReader(teamID, rootID, filename string) (filestore.ReadCloseSeeker, error) {
|
||||||
filePath := filepath.Join(teamID, rootID, filename)
|
filePath := filepath.Join(teamID, rootID, filename)
|
||||||
exists, err := a.filesBackend.FileExists(filePath)
|
exists, err := a.filesBackend.FileExists(filePath)
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -195,8 +196,8 @@ func TestSaveFile(t *testing.T) {
|
|||||||
|
|
||||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||||
paths := strings.Split(path, string(os.PathSeparator))
|
paths := strings.Split(path, string(os.PathSeparator))
|
||||||
assert.Equal(t, "1", paths[0])
|
assert.Equal(t, "boards", paths[0])
|
||||||
assert.Equal(t, testBoardID, paths[1])
|
assert.Equal(t, time.Now().Format("20060102"), paths[1])
|
||||||
fileName = paths[2]
|
fileName = paths[2]
|
||||||
return int64(10)
|
return int64(10)
|
||||||
}
|
}
|
||||||
@ -219,8 +220,8 @@ func TestSaveFile(t *testing.T) {
|
|||||||
|
|
||||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||||
paths := strings.Split(path, string(os.PathSeparator))
|
paths := strings.Split(path, string(os.PathSeparator))
|
||||||
assert.Equal(t, "1", paths[0])
|
assert.Equal(t, "boards", paths[0])
|
||||||
assert.Equal(t, "test-board-id", paths[1])
|
assert.Equal(t, time.Now().Format("20060102"), paths[1])
|
||||||
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
||||||
return int64(10)
|
return int64(10)
|
||||||
}
|
}
|
||||||
@ -243,8 +244,8 @@ func TestSaveFile(t *testing.T) {
|
|||||||
|
|
||||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||||
paths := strings.Split(path, string(os.PathSeparator))
|
paths := strings.Split(path, string(os.PathSeparator))
|
||||||
assert.Equal(t, "1", paths[0])
|
assert.Equal(t, "boards", paths[0])
|
||||||
assert.Equal(t, "test-board-id", paths[1])
|
assert.Equal(t, time.Now().Format("20060102"), paths[1])
|
||||||
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
||||||
return int64(10)
|
return int64(10)
|
||||||
}
|
}
|
||||||
@ -304,3 +305,80 @@ func TestGetFileInfo(t *testing.T) {
|
|||||||
assert.Nil(t, fetchedFileInfo)
|
assert.Nil(t, fetchedFileInfo)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetFile(t *testing.T) {
|
||||||
|
th, _ := SetupTestHelper(t)
|
||||||
|
|
||||||
|
t.Run("when FileInfo exists", func(t *testing.T) {
|
||||||
|
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(&mmModel.FileInfo{
|
||||||
|
Id: "fileInfoID",
|
||||||
|
Path: "/path/to/file/fileName.txt",
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
mockedFileBackend := &mocks.FileBackend{}
|
||||||
|
th.App.filesBackend = mockedFileBackend
|
||||||
|
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
|
||||||
|
readerFunc := func(path string) filestore.ReadCloseSeeker {
|
||||||
|
return mockedReadCloseSeek
|
||||||
|
}
|
||||||
|
|
||||||
|
readerErrorFunc := func(path string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mockedFileBackend.On("Reader", "/path/to/file/fileName.txt").Return(readerFunc, readerErrorFunc)
|
||||||
|
mockedFileBackend.On("FileExists", "/path/to/file/fileName.txt").Return(true, nil)
|
||||||
|
|
||||||
|
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, fileInfo)
|
||||||
|
assert.NotNil(t, seeker)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when FileInfo doesn't exist", func(t *testing.T) {
|
||||||
|
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(nil, nil)
|
||||||
|
|
||||||
|
mockedFileBackend := &mocks.FileBackend{}
|
||||||
|
th.App.filesBackend = mockedFileBackend
|
||||||
|
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
|
||||||
|
readerFunc := func(path string) filestore.ReadCloseSeeker {
|
||||||
|
return mockedReadCloseSeek
|
||||||
|
}
|
||||||
|
|
||||||
|
readerErrorFunc := func(path string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mockedFileBackend.On("Reader", "teamID/boardID/7fileInfoID.txt").Return(readerFunc, readerErrorFunc)
|
||||||
|
mockedFileBackend.On("FileExists", "teamID/boardID/7fileInfoID.txt").Return(true, nil)
|
||||||
|
|
||||||
|
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, fileInfo)
|
||||||
|
assert.NotNil(t, seeker)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when FileInfo exists but FileInfo.Path is not set", func(t *testing.T) {
|
||||||
|
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(&mmModel.FileInfo{
|
||||||
|
Id: "fileInfoID",
|
||||||
|
Path: "",
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
mockedFileBackend := &mocks.FileBackend{}
|
||||||
|
th.App.filesBackend = mockedFileBackend
|
||||||
|
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
|
||||||
|
readerFunc := func(path string) filestore.ReadCloseSeeker {
|
||||||
|
return mockedReadCloseSeek
|
||||||
|
}
|
||||||
|
|
||||||
|
readerErrorFunc := func(path string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mockedFileBackend.On("Reader", "teamID/boardID/7fileInfoID.txt").Return(readerFunc, readerErrorFunc)
|
||||||
|
mockedFileBackend.On("FileExists", "teamID/boardID/7fileInfoID.txt").Return(true, nil)
|
||||||
|
|
||||||
|
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, fileInfo)
|
||||||
|
assert.NotNil(t, seeker)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -380,6 +380,8 @@ func (c *Client) GetCards(boardID string, page int, perPage int) ([]*model.Card,
|
|||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
var cards []*model.Card
|
var cards []*model.Card
|
||||||
if err := json.NewDecoder(r.Body).Decode(&cards); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&cards); err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
@ -398,6 +400,8 @@ func (c *Client) PatchCard(cardID string, cardPatch *model.CardPatch, disableNot
|
|||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
var cardNew *model.Card
|
var cardNew *model.Card
|
||||||
if err := json.NewDecoder(r.Body).Decode(&cardNew); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&cardNew); err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
@ -412,6 +416,8 @@ func (c *Client) GetCard(cardID string) (*model.Card, *Response) {
|
|||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
var card *model.Card
|
var card *model.Card
|
||||||
if err := json.NewDecoder(r.Body).Decode(&card); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&card); err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
@ -450,6 +456,7 @@ func (c *Client) DeleteCategory(teamID, categoryID string) *Response {
|
|||||||
return BuildErrorResponse(r, err)
|
return BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
return BuildResponse(r)
|
return BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1049,6 +1056,7 @@ func (c *Client) HideBoard(teamID, categoryID, boardID string) *Response {
|
|||||||
return BuildErrorResponse(r, err)
|
return BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
return BuildResponse(r)
|
return BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1058,5 +1066,6 @@ func (c *Client) UnhideBoard(teamID, categoryID, boardID string) *Response {
|
|||||||
return BuildErrorResponse(r, err)
|
return BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
return BuildResponse(r)
|
return BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
// It should be maintained in chronological order with most current
|
// It should be maintained in chronological order with most current
|
||||||
// release at the front of the list.
|
// release at the front of the list.
|
||||||
var versions = []string{
|
var versions = []string{
|
||||||
|
"7.10.0",
|
||||||
"7.9.0",
|
"7.9.0",
|
||||||
"7.8.0",
|
"7.8.0",
|
||||||
"7.7.0",
|
"7.7.0",
|
||||||
|
@ -21,11 +21,12 @@ const (
|
|||||||
// query, so we want to stay safely below.
|
// query, so we want to stay safely below.
|
||||||
CategoryInsertBatch = 1000
|
CategoryInsertBatch = 1000
|
||||||
|
|
||||||
TemplatesToTeamsMigrationKey = "TemplatesToTeamsMigrationComplete"
|
TemplatesToTeamsMigrationKey = "TemplatesToTeamsMigrationComplete"
|
||||||
UniqueIDsMigrationKey = "UniqueIDsMigrationComplete"
|
UniqueIDsMigrationKey = "UniqueIDsMigrationComplete"
|
||||||
CategoryUUIDIDMigrationKey = "CategoryUuidIdMigrationComplete"
|
CategoryUUIDIDMigrationKey = "CategoryUuidIdMigrationComplete"
|
||||||
TeamLessBoardsMigrationKey = "TeamLessBoardsMigrationComplete"
|
TeamLessBoardsMigrationKey = "TeamLessBoardsMigrationComplete"
|
||||||
DeletedMembershipBoardsMigrationKey = "DeletedMembershipBoardsMigrationComplete"
|
DeletedMembershipBoardsMigrationKey = "DeletedMembershipBoardsMigrationComplete"
|
||||||
|
DeDuplicateCategoryBoardTableMigrationKey = "DeDuplicateCategoryBoardTableComplete"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *SQLStore) getBlocksWithSameID(db sq.BaseRunner) ([]*model.Block, error) {
|
func (s *SQLStore) getBlocksWithSameID(db sq.BaseRunner) ([]*model.Block, error) {
|
||||||
@ -790,3 +791,102 @@ func (s *SQLStore) getCollationAndCharset(tableName string) (string, string, err
|
|||||||
|
|
||||||
return collation, charSet, nil
|
return collation, charSet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SQLStore) RunDeDuplicateCategoryBoardsMigration(currentMigration int) error {
|
||||||
|
// not supported for SQLite
|
||||||
|
if s.dbType == model.SqliteDBType {
|
||||||
|
if mErr := s.setSystemSetting(s.db, DeDuplicateCategoryBoardTableMigrationKey, strconv.FormatBool(true)); mErr != nil {
|
||||||
|
return fmt.Errorf("cannot mark migration %s as completed: %w", "RunDeDuplicateCategoryBoardsMigration", mErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
setting, err := s.GetSystemSetting(DeDuplicateCategoryBoardTableMigrationKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot get DeDuplicateCategoryBoardTableMigration state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the migration is already completed, do not run it again.
|
||||||
|
if hasAlreadyRun, _ := strconv.ParseBool(setting); hasAlreadyRun {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentMigration >= (deDuplicateCategoryBoards + 1) {
|
||||||
|
// if the migration for which we're fixing the data is already applied,
|
||||||
|
// no need to check fix anything
|
||||||
|
|
||||||
|
if mErr := s.setSystemSetting(s.db, DeDuplicateCategoryBoardTableMigrationKey, strconv.FormatBool(true)); mErr != nil {
|
||||||
|
return fmt.Errorf("cannot mark migration %s as completed: %w", "RunDeDuplicateCategoryBoardsMigration", mErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
needed, err := s.doesDuplicateCategoryBoardsExist()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !needed {
|
||||||
|
if mErr := s.setSystemSetting(s.db, DeDuplicateCategoryBoardTableMigrationKey, strconv.FormatBool(true)); mErr != nil {
|
||||||
|
return fmt.Errorf("cannot mark migration %s as completed: %w", "RunDeDuplicateCategoryBoardsMigration", mErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.dbType == model.MysqlDBType {
|
||||||
|
return s.runMySQLDeDuplicateCategoryBoardsMigration()
|
||||||
|
} else if s.dbType == model.PostgresDBType {
|
||||||
|
return s.runPostgresDeDuplicateCategoryBoardsMigration()
|
||||||
|
}
|
||||||
|
|
||||||
|
if mErr := s.setSystemSetting(s.db, DeDuplicateCategoryBoardTableMigrationKey, strconv.FormatBool(true)); mErr != nil {
|
||||||
|
return fmt.Errorf("cannot mark migration %s as completed: %w", "RunDeDuplicateCategoryBoardsMigration", mErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQLStore) doesDuplicateCategoryBoardsExist() (bool, error) {
|
||||||
|
subQuery := s.getQueryBuilder(s.db).
|
||||||
|
Select("user_id", "board_id", "count(*) AS count").
|
||||||
|
From(s.tablePrefix+"category_boards").
|
||||||
|
GroupBy("user_id", "board_id").
|
||||||
|
Having("count(*) > 1")
|
||||||
|
|
||||||
|
query := s.getQueryBuilder(s.db).
|
||||||
|
Select("COUNT(user_id)").
|
||||||
|
FromSelect(subQuery, "duplicate_dataset")
|
||||||
|
|
||||||
|
row := query.QueryRow()
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
if err := row.Scan(&count); err != nil {
|
||||||
|
s.logger.Error("Error occurred reading number of duplicate records in category_boards table", mlog.Err(err))
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQLStore) runMySQLDeDuplicateCategoryBoardsMigration() error {
|
||||||
|
query := "WITH duplicates AS (SELECT id, ROW_NUMBER() OVER(PARTITION BY user_id, board_id) AS rownum " +
|
||||||
|
"FROM " + s.tablePrefix + "category_boards) " +
|
||||||
|
"DELETE " + s.tablePrefix + "category_boards FROM " + s.tablePrefix + "category_boards " +
|
||||||
|
"JOIN duplicates USING(id) WHERE duplicates.rownum > 1;"
|
||||||
|
if _, err := s.db.Exec(query); err != nil {
|
||||||
|
s.logger.Error("Failed to de-duplicate data in category_boards table", mlog.Err(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQLStore) runPostgresDeDuplicateCategoryBoardsMigration() error {
|
||||||
|
query := "WITH duplicates AS (SELECT id, ROW_NUMBER() OVER(PARTITION BY user_id, board_id) AS rownum " +
|
||||||
|
"FROM " + s.tablePrefix + "category_boards) " +
|
||||||
|
"DELETE FROM " + s.tablePrefix + "category_boards USING duplicates " +
|
||||||
|
"WHERE " + s.tablePrefix + "category_boards.id = duplicates.id AND duplicates.rownum > 1;"
|
||||||
|
if _, err := s.db.Exec(query); err != nil {
|
||||||
|
s.logger.Error("Failed to de-duplicate data in category_boards table", mlog.Err(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -22,6 +22,7 @@ func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) er
|
|||||||
"extension",
|
"extension",
|
||||||
"size",
|
"size",
|
||||||
"delete_at",
|
"delete_at",
|
||||||
|
"path",
|
||||||
"archived",
|
"archived",
|
||||||
).
|
).
|
||||||
Values(
|
Values(
|
||||||
@ -31,6 +32,7 @@ func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) er
|
|||||||
fileInfo.Extension,
|
fileInfo.Extension,
|
||||||
fileInfo.Size,
|
fileInfo.Size,
|
||||||
fileInfo.DeleteAt,
|
fileInfo.DeleteAt,
|
||||||
|
fileInfo.Path,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -57,6 +59,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo,
|
|||||||
"extension",
|
"extension",
|
||||||
"size",
|
"size",
|
||||||
"archived",
|
"archived",
|
||||||
|
"path",
|
||||||
).
|
).
|
||||||
From(s.tablePrefix + "file_info").
|
From(s.tablePrefix + "file_info").
|
||||||
Where(sq.Eq{"Id": id})
|
Where(sq.Eq{"Id": id})
|
||||||
@ -73,6 +76,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo,
|
|||||||
&fileInfo.Extension,
|
&fileInfo.Extension,
|
||||||
&fileInfo.Size,
|
&fileInfo.Size,
|
||||||
&fileInfo.Archived,
|
&fileInfo.Archived,
|
||||||
|
&fileInfo.Path,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -36,6 +36,7 @@ const (
|
|||||||
uniqueIDsMigrationRequiredVersion = 14
|
uniqueIDsMigrationRequiredVersion = 14
|
||||||
teamLessBoardsMigrationRequiredVersion = 18
|
teamLessBoardsMigrationRequiredVersion = 18
|
||||||
categoriesUUIDIDMigrationRequiredVersion = 20
|
categoriesUUIDIDMigrationRequiredVersion = 20
|
||||||
|
deDuplicateCategoryBoards = 35
|
||||||
|
|
||||||
tempSchemaMigrationTableName = "temp_schema_migration"
|
tempSchemaMigrationTableName = "temp_schema_migration"
|
||||||
)
|
)
|
||||||
@ -248,6 +249,15 @@ func (s *SQLStore) runMigrationSequence(engine *morph.Morph, driver drivers.Driv
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mErr := s.ensureMigrationsAppliedUpToVersion(engine, driver, deDuplicateCategoryBoards); mErr != nil {
|
||||||
|
return mErr
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMigrationVersion := len(appliedMigrations)
|
||||||
|
if mErr := s.RunDeDuplicateCategoryBoardsMigration(currentMigrationVersion); mErr != nil {
|
||||||
|
return mErr
|
||||||
|
}
|
||||||
|
|
||||||
s.logger.Debug("== Applying all remaining migrations ====================",
|
s.logger.Debug("== Applying all remaining migrations ====================",
|
||||||
mlog.Int("current_version", len(appliedMigrations)),
|
mlog.Int("current_version", len(appliedMigrations)),
|
||||||
)
|
)
|
||||||
@ -309,7 +319,7 @@ func (s *SQLStore) GetTemplateHelperFuncs() template.FuncMap {
|
|||||||
|
|
||||||
func (s *SQLStore) genAddColumnIfNeeded(tableName, columnName, datatype, constraint string) (string, error) {
|
func (s *SQLStore) genAddColumnIfNeeded(tableName, columnName, datatype, constraint string) (string, error) {
|
||||||
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
||||||
normTableName := normalizeTablename(s.schemaName, tableName)
|
normTableName := s.normalizeTablename(tableName)
|
||||||
|
|
||||||
switch s.dbType {
|
switch s.dbType {
|
||||||
case model.SqliteDBType:
|
case model.SqliteDBType:
|
||||||
@ -348,7 +358,7 @@ func (s *SQLStore) genAddColumnIfNeeded(tableName, columnName, datatype, constra
|
|||||||
|
|
||||||
func (s *SQLStore) genDropColumnIfNeeded(tableName, columnName string) (string, error) {
|
func (s *SQLStore) genDropColumnIfNeeded(tableName, columnName string) (string, error) {
|
||||||
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
||||||
normTableName := normalizeTablename(s.schemaName, tableName)
|
normTableName := s.normalizeTablename(tableName)
|
||||||
|
|
||||||
switch s.dbType {
|
switch s.dbType {
|
||||||
case model.SqliteDBType:
|
case model.SqliteDBType:
|
||||||
@ -385,7 +395,7 @@ func (s *SQLStore) genDropColumnIfNeeded(tableName, columnName string) (string,
|
|||||||
func (s *SQLStore) genCreateIndexIfNeeded(tableName, columns string) (string, error) {
|
func (s *SQLStore) genCreateIndexIfNeeded(tableName, columns string) (string, error) {
|
||||||
indexName := getIndexName(tableName, columns)
|
indexName := getIndexName(tableName, columns)
|
||||||
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
||||||
normTableName := normalizeTablename(s.schemaName, tableName)
|
normTableName := s.normalizeTablename(tableName)
|
||||||
|
|
||||||
switch s.dbType {
|
switch s.dbType {
|
||||||
case model.SqliteDBType:
|
case model.SqliteDBType:
|
||||||
@ -425,7 +435,7 @@ func (s *SQLStore) genRenameTableIfNeeded(oldTableName, newTableName string) (st
|
|||||||
oldTableName = addPrefixIfNeeded(oldTableName, s.tablePrefix)
|
oldTableName = addPrefixIfNeeded(oldTableName, s.tablePrefix)
|
||||||
newTableName = addPrefixIfNeeded(newTableName, s.tablePrefix)
|
newTableName = addPrefixIfNeeded(newTableName, s.tablePrefix)
|
||||||
|
|
||||||
normOldTableName := normalizeTablename(s.schemaName, oldTableName)
|
normOldTableName := s.normalizeTablename(oldTableName)
|
||||||
|
|
||||||
vars := map[string]string{
|
vars := map[string]string{
|
||||||
"schema": s.schemaName,
|
"schema": s.schemaName,
|
||||||
@ -456,14 +466,14 @@ func (s *SQLStore) genRenameTableIfNeeded(oldTableName, newTableName string) (st
|
|||||||
case model.PostgresDBType:
|
case model.PostgresDBType:
|
||||||
return replaceVars(`
|
return replaceVars(`
|
||||||
do $$
|
do $$
|
||||||
begin
|
begin
|
||||||
if (SELECT COUNT(table_name) FROM INFORMATION_SCHEMA.TABLES
|
if (SELECT COUNT(table_name) FROM INFORMATION_SCHEMA.TABLES
|
||||||
WHERE table_name = '[[new_table_name]]'
|
WHERE table_name = '[[new_table_name]]'
|
||||||
AND table_schema = '[[schema]]'
|
AND table_schema = '[[schema]]'
|
||||||
) = 0 then
|
) = 0 then
|
||||||
ALTER TABLE [[norm_old_table_name]] RENAME TO [[new_table_name]];
|
ALTER TABLE [[norm_old_table_name]] RENAME TO [[new_table_name]];
|
||||||
end if;
|
end if;
|
||||||
end$$;
|
end$$;
|
||||||
`, vars), nil
|
`, vars), nil
|
||||||
default:
|
default:
|
||||||
return "", ErrUnsupportedDatabaseType
|
return "", ErrUnsupportedDatabaseType
|
||||||
@ -472,7 +482,7 @@ func (s *SQLStore) genRenameTableIfNeeded(oldTableName, newTableName string) (st
|
|||||||
|
|
||||||
func (s *SQLStore) genRenameColumnIfNeeded(tableName, oldColumnName, newColumnName, dataType string) (string, error) {
|
func (s *SQLStore) genRenameColumnIfNeeded(tableName, oldColumnName, newColumnName, dataType string) (string, error) {
|
||||||
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
||||||
normTableName := normalizeTablename(s.schemaName, tableName)
|
normTableName := s.normalizeTablename(tableName)
|
||||||
|
|
||||||
vars := map[string]string{
|
vars := map[string]string{
|
||||||
"schema": s.schemaName,
|
"schema": s.schemaName,
|
||||||
@ -506,15 +516,15 @@ func (s *SQLStore) genRenameColumnIfNeeded(tableName, oldColumnName, newColumnNa
|
|||||||
case model.PostgresDBType:
|
case model.PostgresDBType:
|
||||||
return replaceVars(`
|
return replaceVars(`
|
||||||
do $$
|
do $$
|
||||||
begin
|
begin
|
||||||
if (SELECT COUNT(table_name) FROM INFORMATION_SCHEMA.COLUMNS
|
if (SELECT COUNT(table_name) FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
WHERE table_name = '[[table_name]]'
|
WHERE table_name = '[[table_name]]'
|
||||||
AND table_schema = '[[schema]]'
|
AND table_schema = '[[schema]]'
|
||||||
AND column_name = '[[new_column_name]]'
|
AND column_name = '[[new_column_name]]'
|
||||||
) = 0 then
|
) = 0 then
|
||||||
ALTER TABLE [[norm_table_name]] RENAME COLUMN [[old_column_name]] TO [[new_column_name]];
|
ALTER TABLE [[norm_table_name]] RENAME COLUMN [[old_column_name]] TO [[new_column_name]];
|
||||||
end if;
|
end if;
|
||||||
end$$;
|
end$$;
|
||||||
`, vars), nil
|
`, vars), nil
|
||||||
default:
|
default:
|
||||||
return "", ErrUnsupportedDatabaseType
|
return "", ErrUnsupportedDatabaseType
|
||||||
@ -610,7 +620,7 @@ func (s *SQLStore) doesColumnExist(tableName, columnName string) (bool, error) {
|
|||||||
|
|
||||||
func (s *SQLStore) genAddConstraintIfNeeded(tableName, constraintName, constraintType, constraintDefinition string) (string, error) {
|
func (s *SQLStore) genAddConstraintIfNeeded(tableName, constraintName, constraintType, constraintDefinition string) (string, error) {
|
||||||
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
||||||
normTableName := normalizeTablename(s.schemaName, tableName)
|
normTableName := s.normalizeTablename(tableName)
|
||||||
|
|
||||||
var query string
|
var query string
|
||||||
|
|
||||||
@ -676,8 +686,12 @@ func addPrefixIfNeeded(s, prefix string) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeTablename(schemaName, tableName string) string {
|
func (s *SQLStore) normalizeTablename(tableName string) string {
|
||||||
if schemaName != "" && !strings.HasPrefix(tableName, schemaName+".") {
|
if s.schemaName != "" && !strings.HasPrefix(tableName, s.schemaName+".") {
|
||||||
|
schemaName := s.schemaName
|
||||||
|
if s.dbType == model.MysqlDBType {
|
||||||
|
schemaName = "`" + schemaName + "`"
|
||||||
|
}
|
||||||
tableName = schemaName + "." + tableName
|
tableName = schemaName + "." + tableName
|
||||||
}
|
}
|
||||||
return tableName
|
return tableName
|
||||||
|
@ -23,4 +23,4 @@
|
|||||||
SELECT id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden FROM {{.prefix}}category_boards_old;
|
SELECT id, user_id, category_id, board_id, create_at, update_at, sort_order, hidden FROM {{.prefix}}category_boards_old;
|
||||||
DROP TABLE {{.prefix}}category_boards_old;
|
DROP TABLE {{.prefix}}category_boards_old;
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
SELECT 1;
|
@ -0,0 +1 @@
|
|||||||
|
{{ addColumnIfNeeded "file_info" "path" "varchar(512)" "" }}
|
@ -10,8 +10,9 @@ import (
|
|||||||
// these system settings are created when running the data migrations,
|
// these system settings are created when running the data migrations,
|
||||||
// so they will be present after the tests setup.
|
// so they will be present after the tests setup.
|
||||||
var dataMigrationSystemSettings = map[string]string{
|
var dataMigrationSystemSettings = map[string]string{
|
||||||
"UniqueIDsMigrationComplete": "true",
|
"UniqueIDsMigrationComplete": "true",
|
||||||
"CategoryUuidIdMigrationComplete": "true",
|
"CategoryUuidIdMigrationComplete": "true",
|
||||||
|
"DeDuplicateCategoryBoardTableComplete": "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
func addBaseSettings(m map[string]string) map[string]string {
|
func addBaseSettings(m map[string]string) map[string]string {
|
||||||
|
@ -2,6 +2,7 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -120,3 +121,7 @@ func DedupeStringArr(arr []string) []string {
|
|||||||
|
|
||||||
return dedupedArr
|
return dedupedArr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetBaseFilePath() string {
|
||||||
|
return path.Join("boards", time.Now().Format("20060102"))
|
||||||
|
}
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
{
|
{
|
||||||
"AppBar.Tooltip": "اخيار الالواح المرتبطة",
|
"AppBar.Tooltip": "اخيار الالواح المرتبطة",
|
||||||
|
"Attachment.Attachment-title": "المرفق",
|
||||||
|
"AttachmentBlock.DeleteAction": "حذف",
|
||||||
|
"AttachmentBlock.addElement": "اضافة {type}",
|
||||||
|
"AttachmentBlock.delete": "تم حذف المرفق.",
|
||||||
|
"AttachmentBlock.failed": "تعذر تحميل هذا الملف حيث تم الوصول إلى الحد الأقصى لحجم الملفات.",
|
||||||
|
"AttachmentBlock.upload": "يتم الآن تحميل المرفق.",
|
||||||
|
"AttachmentBlock.uploadSuccess": "تم تحميل المرفق.",
|
||||||
|
"AttachmentElement.delete-confirmation-dialog-button-text": "حذف",
|
||||||
|
"AttachmentElement.download": "تحميل",
|
||||||
|
"AttachmentElement.upload-percentage": "جاري التحمي... ({uploadPercent}%)",
|
||||||
"BoardComponent.add-a-group": "+ إضافة مجموعة",
|
"BoardComponent.add-a-group": "+ إضافة مجموعة",
|
||||||
"BoardComponent.delete": "حذف",
|
"BoardComponent.delete": "حذف",
|
||||||
"BoardComponent.hidden-columns": "الأعمدة المخفية",
|
"BoardComponent.hidden-columns": "الأعمدة المخفية",
|
||||||
@ -155,9 +165,15 @@
|
|||||||
"FindBoardsDialog.NoResultsFor": "لا يوجد نتيجة للبحث \"{searchQuery}\"",
|
"FindBoardsDialog.NoResultsFor": "لا يوجد نتيجة للبحث \"{searchQuery}\"",
|
||||||
"FindBoardsDialog.NoResultsSubtext": "اختر بحث آخر أو تأكد من الأخطاء الإملائية.",
|
"FindBoardsDialog.NoResultsSubtext": "اختر بحث آخر أو تأكد من الأخطاء الإملائية.",
|
||||||
"FindBoardsDialog.Title": "البحث عن ألواح",
|
"FindBoardsDialog.Title": "البحث عن ألواح",
|
||||||
|
"GroupBy.ungroup": "إلغاء التجميع",
|
||||||
|
"KanbanCard.untitled": "بدون عنوان",
|
||||||
|
"Mutator.new-card-from-template": "بطاقة جديدة من نموذج",
|
||||||
|
"Mutator.new-template-from-card": "نموذج جديد من بطاقة",
|
||||||
"OnboardingTour.AddComments.Title": "إضافة تعليقات",
|
"OnboardingTour.AddComments.Title": "إضافة تعليقات",
|
||||||
"OnboardingTour.AddDescription.Title": "اضافة وصف",
|
"OnboardingTour.AddDescription.Title": "اضافة وصف",
|
||||||
"OnboardingTour.AddProperties.Title": "إضافة خواص",
|
"OnboardingTour.AddProperties.Title": "إضافة خواص",
|
||||||
|
"OnboardingTour.AddView.Body": "انتقل هنا لإنشاء عرض جديد لتنظيم لوحتك باستخدام تخطيطات مختلفة.",
|
||||||
|
"OnboardingTour.AddView.Title": "إضافة عرض جديد",
|
||||||
"OnboardingTour.CopyLink.Title": "نسخ الرابط",
|
"OnboardingTour.CopyLink.Title": "نسخ الرابط",
|
||||||
"PropertyMenu.Delete": "حذف",
|
"PropertyMenu.Delete": "حذف",
|
||||||
"PropertyMenu.changeType": "تغيير نوع الخاصية",
|
"PropertyMenu.changeType": "تغيير نوع الخاصية",
|
||||||
|
@ -1,17 +1,81 @@
|
|||||||
{
|
{
|
||||||
|
"Attachment.Attachment-title": "Adjunt",
|
||||||
|
"AttachmentBlock.DeleteAction": "esborra",
|
||||||
|
"AttachmentBlock.addElement": "afegir {type}",
|
||||||
|
"AttachmentBlock.delete": "Adjunt esborrat.",
|
||||||
|
"AttachmentBlock.failed": "Aquest fitxer no pot ser afegit ja que el límit de tamany de fitxer ha estat assolit.",
|
||||||
|
"AttachmentBlock.upload": "Adjunt afegint-se.",
|
||||||
|
"AttachmentBlock.uploadSuccess": "Adjunt afegit.",
|
||||||
|
"AttachmentElement.delete-confirmation-dialog-button-text": "Esborra",
|
||||||
|
"AttachmentElement.download": "Descarrega",
|
||||||
|
"AttachmentElement.upload-percentage": "Afegint...({uploadPercent}%)",
|
||||||
"BoardComponent.add-a-group": "+ Afegir un grup",
|
"BoardComponent.add-a-group": "+ Afegir un grup",
|
||||||
"BoardComponent.delete": "Eliminar",
|
"BoardComponent.delete": "Eliminar",
|
||||||
"BoardComponent.hidden-columns": "Columnes ocultes",
|
"BoardComponent.hidden-columns": "Columnes ocultes",
|
||||||
"BoardComponent.hide": "Amagar",
|
"BoardComponent.hide": "Amagar",
|
||||||
"BoardComponent.new": "+ Nou",
|
"BoardComponent.new": "+ Nou",
|
||||||
"BoardComponent.no-property": "Sense {property}",
|
"BoardComponent.no-property": "Sense {property}",
|
||||||
"BoardComponent.no-property-title": "Els elements amb una propietat {property} buida anirán aquí. Aquesta col·lumna no pot elimiar-se.",
|
"BoardComponent.no-property-title": "Els elements amb una propietat {property} buida anirán aquí. Aquesta col·lumna no pot eliminar-se.",
|
||||||
"BoardComponent.show": "Mostrar",
|
"BoardComponent.show": "Mostrar",
|
||||||
|
"BoardMember.schemeAdmin": "Admin",
|
||||||
|
"BoardMember.schemeCommenter": "Comentarista",
|
||||||
|
"BoardMember.schemeEditor": "Editor",
|
||||||
|
"BoardMember.schemeNone": "Cap",
|
||||||
|
"BoardMember.schemeViewer": "Visualitzador",
|
||||||
|
"BoardPage.newVersion": "Una nova versió de Boards és disponible, clica aquí per recarregar.",
|
||||||
|
"BoardPage.syncFailed": "El tauler podria ser eliminat o revocat l'accés.",
|
||||||
|
"BoardTemplateSelector.add-template": "Crea una nova plantilla",
|
||||||
|
"BoardTemplateSelector.create-empty-board": "Crea un taulell buit",
|
||||||
|
"BoardTemplateSelector.delete-template": "Esborra",
|
||||||
|
"BoardTemplateSelector.description": "Afegeix el taulell a la barra lateral usant alguna de les plantilles definides a sota o comença des de zero.",
|
||||||
|
"BoardTemplateSelector.edit-template": "Edita",
|
||||||
|
"BoardTemplateSelector.plugin.no-content-description": "Afegeix el taulell a la barra lateral usant alguna de les plantilles definides a sota o comença des de zero.",
|
||||||
|
"BoardTemplateSelector.plugin.no-content-title": "Crea un taulell",
|
||||||
|
"BoardTemplateSelector.title": "Crea un taulell",
|
||||||
|
"BoardTemplateSelector.use-this-template": "Utilitza aquesta plantilla",
|
||||||
|
"BoardsSwitcher.Title": "Busca taulells",
|
||||||
|
"BoardsUnfurl.Updated": "Actualitzat {time}",
|
||||||
|
"Calculations.Options.average.displayName": "Promig",
|
||||||
|
"Calculations.Options.average.label": "Promig",
|
||||||
|
"Calculations.Options.countChecked.displayName": "Comprovat",
|
||||||
|
"Calculations.Options.countUniqueValue.displayName": "Únic",
|
||||||
|
"Calculations.Options.countUniqueValue.label": "Compta valors únics",
|
||||||
|
"Calculations.Options.countValue.displayName": "Valors",
|
||||||
|
"Calculations.Options.dateRange.displayName": "Rang",
|
||||||
|
"Calculations.Options.dateRange.label": "Rang",
|
||||||
|
"Calculations.Options.earliest.displayName": "Proper",
|
||||||
|
"Calculations.Options.earliest.label": "Proper",
|
||||||
|
"Calculations.Options.latest.displayName": "Últim",
|
||||||
|
"Calculations.Options.latest.label": "Últim",
|
||||||
|
"Calculations.Options.max.displayName": "Màxim",
|
||||||
|
"Calculations.Options.max.label": "Màxim",
|
||||||
|
"Calculations.Options.min.displayName": "Mínim",
|
||||||
|
"Calculations.Options.min.label": "Mínim",
|
||||||
|
"Calculations.Options.none.displayName": "Calcula",
|
||||||
|
"Calculations.Options.none.label": "Cap",
|
||||||
|
"Calculations.Options.percentChecked.displayName": "Completat",
|
||||||
|
"Calculations.Options.percentChecked.label": "Percentatge completat",
|
||||||
|
"Calculations.Options.percentUnchecked.displayName": "No finalitzat",
|
||||||
|
"Calculations.Options.percentUnchecked.label": "Percentatge no finalitzat",
|
||||||
|
"Calculations.Options.range.displayName": "Rang",
|
||||||
|
"Calculations.Options.range.label": "Rang",
|
||||||
|
"Calculations.Options.sum.displayName": "Suma",
|
||||||
|
"Calculations.Options.sum.label": "Suma",
|
||||||
|
"CalendarCard.untitled": "Sense títol",
|
||||||
|
"CardActionsMenu.copiedLink": "Copiat!",
|
||||||
|
"CardActionsMenu.copyLink": "Còpia l'enllaç",
|
||||||
|
"CardActionsMenu.delete": "Esborra",
|
||||||
|
"CardActionsMenu.duplicate": "Duplica",
|
||||||
|
"CardBadges.title-comments": "Comentaris",
|
||||||
|
"CardBadges.title-description": "Aquesta tarjeta té una descripció",
|
||||||
|
"CardDetail.Attach": "Adjunta",
|
||||||
|
"CardDetail.Follow": "Segueix",
|
||||||
|
"CardDetail.Following": "Segueix",
|
||||||
"CardDetail.add-content": "Afegeix contingut",
|
"CardDetail.add-content": "Afegeix contingut",
|
||||||
"CardDetail.add-icon": "Afegeix icona",
|
"CardDetail.add-icon": "Afegeix icona",
|
||||||
"CardDetail.add-property": "+ Afegeix propietat",
|
"CardDetail.add-property": "+ Afegeix propietat",
|
||||||
"CardDetail.addCardText": "afegeix text a la targeta",
|
"CardDetail.addCardText": "afegeix text a la targeta",
|
||||||
"CardDetail.moveContent": "mou el contingut de la targeta",
|
"CardDetail.moveContent": "Mou el contingut de la targeta",
|
||||||
"CardDetail.new-comment-placeholder": "Afegeix un comentari...",
|
"CardDetail.new-comment-placeholder": "Afegeix un comentari...",
|
||||||
"CardDialog.editing-template": "Estas editant una plantilla.",
|
"CardDialog.editing-template": "Estas editant una plantilla.",
|
||||||
"CardDialog.nocard": "Aquesta targeta no existeix o és innaccesible.",
|
"CardDialog.nocard": "Aquesta targeta no existeix o és innaccesible.",
|
||||||
@ -47,18 +111,18 @@
|
|||||||
"PropertyMenu.changeType": "Canviar el tipus de propietat",
|
"PropertyMenu.changeType": "Canviar el tipus de propietat",
|
||||||
"PropertyMenu.typeTitle": "Tipus",
|
"PropertyMenu.typeTitle": "Tipus",
|
||||||
"PropertyType.Checkbox": "casella de verificació",
|
"PropertyType.Checkbox": "casella de verificació",
|
||||||
"PropertyType.CreatedBy": "Creat Per",
|
"PropertyType.CreatedBy": "Creada per",
|
||||||
"PropertyType.CreatedTime": "Moment de creació",
|
"PropertyType.CreatedTime": "Moment de creació",
|
||||||
"PropertyType.Date": "Data",
|
"PropertyType.Date": "Data",
|
||||||
"PropertyType.Email": "Correu electrònic",
|
"PropertyType.Email": "Correu electrònic",
|
||||||
"PropertyType.MultiSelect": "Multi selecció",
|
"PropertyType.MultiSelect": "Selecció múltiple",
|
||||||
"PropertyType.Number": "Número",
|
"PropertyType.Number": "Número",
|
||||||
"PropertyType.Person": "Persona",
|
"PropertyType.Person": "Persona",
|
||||||
"PropertyType.Phone": "Telèfon",
|
"PropertyType.Phone": "Telèfon",
|
||||||
"PropertyType.Select": "Selecciona",
|
"PropertyType.Select": "Selecciona",
|
||||||
"PropertyType.Text": "Text",
|
"PropertyType.Text": "Text",
|
||||||
"PropertyType.UpdatedBy": "Actualitzat per",
|
"PropertyType.UpdatedBy": "Última actualització feta per",
|
||||||
"PropertyType.UpdatedTime": "Moment de actualització",
|
"PropertyType.UpdatedTime": "Moment d'actualització",
|
||||||
"RegistrationLink.confirmRegenerateToken": "Això invalidarà enllaços compartits anteriorment. Continuar?",
|
"RegistrationLink.confirmRegenerateToken": "Això invalidarà enllaços compartits anteriorment. Continuar?",
|
||||||
"RegistrationLink.copiedLink": "Copiat!",
|
"RegistrationLink.copiedLink": "Copiat!",
|
||||||
"RegistrationLink.copyLink": "Copiar enllaç",
|
"RegistrationLink.copyLink": "Copiar enllaç",
|
||||||
@ -113,7 +177,7 @@
|
|||||||
"ViewHeader.group-by": "Agrupar per: {property}",
|
"ViewHeader.group-by": "Agrupar per: {property}",
|
||||||
"ViewHeader.new": "Nou",
|
"ViewHeader.new": "Nou",
|
||||||
"ViewHeader.properties": "Propietats",
|
"ViewHeader.properties": "Propietats",
|
||||||
"ViewHeader.search-text": "Cercar text",
|
"ViewHeader.search-text": "Cerca tarjetes",
|
||||||
"ViewHeader.select-a-template": "Selecciona una plantilla",
|
"ViewHeader.select-a-template": "Selecciona una plantilla",
|
||||||
"ViewHeader.sort": "Ordenar",
|
"ViewHeader.sort": "Ordenar",
|
||||||
"ViewHeader.untitled": "Sense títol",
|
"ViewHeader.untitled": "Sense títol",
|
||||||
|
@ -1,4 +1,15 @@
|
|||||||
{
|
{
|
||||||
|
"AppBar.Tooltip": "Alternar tableros vinculados",
|
||||||
|
"Attachment.Attachment-title": "Archivos adjuntos",
|
||||||
|
"AttachmentBlock.DeleteAction": "borrar",
|
||||||
|
"AttachmentBlock.addElement": "agregar {type}",
|
||||||
|
"AttachmentBlock.delete": "Archivo adjunto eliminado.",
|
||||||
|
"AttachmentBlock.failed": "Este archivo no puede subirse debido a que excede el límite de tamaño de archivo.",
|
||||||
|
"AttachmentBlock.upload": "Subiendo archivo adjunto.",
|
||||||
|
"AttachmentBlock.uploadSuccess": "Archivo adjunto subido.",
|
||||||
|
"AttachmentElement.delete-confirmation-dialog-button-text": "Borrar",
|
||||||
|
"AttachmentElement.download": "Descargar",
|
||||||
|
"AttachmentElement.upload-percentage": "Subiendo...({uploadPercent}%)",
|
||||||
"BoardComponent.add-a-group": "+ Añadir un grupo",
|
"BoardComponent.add-a-group": "+ Añadir un grupo",
|
||||||
"BoardComponent.delete": "Borrar",
|
"BoardComponent.delete": "Borrar",
|
||||||
"BoardComponent.hidden-columns": "Columnas Ocultas",
|
"BoardComponent.hidden-columns": "Columnas Ocultas",
|
||||||
@ -10,21 +21,39 @@
|
|||||||
"BoardMember.schemeAdmin": "Administrador",
|
"BoardMember.schemeAdmin": "Administrador",
|
||||||
"BoardMember.schemeEditor": "Editor",
|
"BoardMember.schemeEditor": "Editor",
|
||||||
"BoardMember.schemeNone": "Ninguno",
|
"BoardMember.schemeNone": "Ninguno",
|
||||||
"BoardPage.newVersion": "Una nueva versión de Board está disponible, haz click aquí para recargar.",
|
"BoardMember.schemeViewer": "Visualizador",
|
||||||
"BoardPage.syncFailed": "El tablero puede estar eliminado o el acceso fue revocado.",
|
"BoardMember.unlinkChannel": "Desvincular",
|
||||||
"BoardTemplateSelector.add-template": "Nueva plantilla",
|
"BoardPage.newVersion": "Una nueva versión de Boards está disponible, haz clic aquí para recargar.",
|
||||||
"BoardTemplateSelector.create-empty-board": "Crear pizarra vacía",
|
"BoardPage.syncFailed": "El tablero puede haber sido eliminado o el acceso revocado.",
|
||||||
"BoardTemplateSelector.delete-template": "Suprimir",
|
"BoardTemplateSelector.add-template": "Crear nueva plantilla",
|
||||||
|
"BoardTemplateSelector.create-empty-board": "Crear un tablero vacío",
|
||||||
|
"BoardTemplateSelector.delete-template": "Eliminar",
|
||||||
|
"BoardTemplateSelector.description": "Agregar un tablero a la barra lateral usando alguna de las plantillas definidas a continuación o empezar desde cero.",
|
||||||
"BoardTemplateSelector.edit-template": "Editar",
|
"BoardTemplateSelector.edit-template": "Editar",
|
||||||
|
"BoardTemplateSelector.plugin.no-content-description": "Agregar un tablero a la barra lateral usando alguna de las plantillas definidas a continuación o empezar desde cero.",
|
||||||
|
"BoardTemplateSelector.plugin.no-content-title": "Crear un tablero",
|
||||||
|
"BoardTemplateSelector.title": "Crear un tablero",
|
||||||
"BoardTemplateSelector.use-this-template": "Utiliza esta plantilla",
|
"BoardTemplateSelector.use-this-template": "Utiliza esta plantilla",
|
||||||
|
"BoardsSwitcher.Title": "Encontrar tableros",
|
||||||
|
"BoardsUnfurl.Limited": "Los detalles adicionales están ocultos debido a que la tarjeta ha sido archivada",
|
||||||
"BoardsUnfurl.Updated": "Actualizado {time}",
|
"BoardsUnfurl.Updated": "Actualizado {time}",
|
||||||
"Calculations.Options.count.displayName": "Cantidad",
|
"Calculations.Options.average.displayName": "Promedio",
|
||||||
"Calculations.Options.count.label": "Cantidad",
|
"Calculations.Options.average.label": "Promedio",
|
||||||
|
"Calculations.Options.count.displayName": "Contar",
|
||||||
|
"Calculations.Options.count.label": "Contar",
|
||||||
"Calculations.Options.countChecked.displayName": "Marcado",
|
"Calculations.Options.countChecked.displayName": "Marcado",
|
||||||
|
"Calculations.Options.countChecked.label": "Contar marcados",
|
||||||
"Calculations.Options.countUnchecked.displayName": "Deseleccionado",
|
"Calculations.Options.countUnchecked.displayName": "Deseleccionado",
|
||||||
|
"Calculations.Options.countUnchecked.label": "Contar no marcados",
|
||||||
|
"Calculations.Options.countUniqueValue.displayName": "Único",
|
||||||
|
"Calculations.Options.countUniqueValue.label": "Contar valores únicos",
|
||||||
"Calculations.Options.countValue.displayName": "Valores",
|
"Calculations.Options.countValue.displayName": "Valores",
|
||||||
"Calculations.Options.dateRange.displayName": "Rango",
|
"Calculations.Options.dateRange.displayName": "Rango",
|
||||||
"Calculations.Options.dateRange.label": "Rango",
|
"Calculations.Options.dateRange.label": "Rango",
|
||||||
|
"Calculations.Options.earliest.displayName": "Más antiguo",
|
||||||
|
"Calculations.Options.earliest.label": "Más antiguo",
|
||||||
|
"Calculations.Options.latest.displayName": "Último",
|
||||||
|
"Calculations.Options.latest.label": "Último",
|
||||||
"Calculations.Options.max.displayName": "Máx",
|
"Calculations.Options.max.displayName": "Máx",
|
||||||
"Calculations.Options.max.label": "Máx",
|
"Calculations.Options.max.label": "Máx",
|
||||||
"Calculations.Options.median.displayName": "Mediana",
|
"Calculations.Options.median.displayName": "Mediana",
|
||||||
@ -34,17 +63,54 @@
|
|||||||
"Calculations.Options.none.displayName": "Calcular",
|
"Calculations.Options.none.displayName": "Calcular",
|
||||||
"Calculations.Options.none.label": "Ninguna",
|
"Calculations.Options.none.label": "Ninguna",
|
||||||
"Calculations.Options.percentChecked.displayName": "Marcado",
|
"Calculations.Options.percentChecked.displayName": "Marcado",
|
||||||
|
"Calculations.Options.percentChecked.label": "Porcentaje marcado",
|
||||||
|
"Calculations.Options.percentUnchecked.displayName": "Desmarcado",
|
||||||
|
"Calculations.Options.percentUnchecked.label": "Porcentaje desmarcado",
|
||||||
"Calculations.Options.range.displayName": "Rango",
|
"Calculations.Options.range.displayName": "Rango",
|
||||||
|
"Calculations.Options.range.label": "Rango",
|
||||||
|
"Calculations.Options.sum.displayName": "Suma",
|
||||||
|
"Calculations.Options.sum.label": "Suma",
|
||||||
|
"CalendarCard.untitled": "Sin título",
|
||||||
|
"CardActionsMenu.copiedLink": "¡Copiado!",
|
||||||
|
"CardActionsMenu.copyLink": "Copiar hipervínculo",
|
||||||
|
"CardActionsMenu.delete": "Eliminar",
|
||||||
|
"CardActionsMenu.duplicate": "Duplicar",
|
||||||
|
"CardBadges.title-checkboxes": "Casillas de verificación",
|
||||||
|
"CardBadges.title-comments": "Comentarios",
|
||||||
|
"CardBadges.title-description": "Esta tarjeta tiene una descripción",
|
||||||
|
"CardDetail.Attach": "Adjuntar",
|
||||||
|
"CardDetail.Follow": "Seguir",
|
||||||
|
"CardDetail.Following": "Siguiendo",
|
||||||
"CardDetail.add-content": "Añadir contenido",
|
"CardDetail.add-content": "Añadir contenido",
|
||||||
"CardDetail.add-icon": "Añadir icono",
|
"CardDetail.add-icon": "Añadir icono",
|
||||||
"CardDetail.add-property": "+ Añadir propiedad",
|
"CardDetail.add-property": "+ Añadir propiedad",
|
||||||
"CardDetail.addCardText": "añade texto a la tarjeta",
|
"CardDetail.addCardText": "agregar texto a la tarjeta",
|
||||||
"CardDetail.moveContent": "mover contenido de la tarjeta",
|
"CardDetail.limited-body": "Mejorar a nuestro plan Professional o Enterprise.",
|
||||||
|
"CardDetail.limited-button": "Mejorar",
|
||||||
|
"CardDetail.limited-title": "Esta tarjeta está oculta",
|
||||||
|
"CardDetail.moveContent": "Mover contenido de la tarjeta",
|
||||||
"CardDetail.new-comment-placeholder": "Añadir un comentario...",
|
"CardDetail.new-comment-placeholder": "Añadir un comentario...",
|
||||||
"CardDetailProperty.confirm-delete-subtext": "¿Estas seguro de que quieres eliminar la propiedad \"{nombre de la propiedad}\"? Al eliminarla se borrará la propiedad de todas las tarjetas de este tablero.",
|
"CardDetailProperty.confirm-delete-heading": "Confirmar eliminación de la propiedad",
|
||||||
"CardDetailProperty.property-deleted": "¡{nombre de la propiedad} ha sido eliminado exitosamente!",
|
"CardDetailProperty.confirm-delete-subtext": "¿Estás seguro de que quieres eliminar la propiedad \"{propertyName}\"? Al eliminarla también se removerá la propiedad en todas las tarjetas de este tablero.",
|
||||||
|
"CardDetailProperty.confirm-property-name-change-subtext": "¿Estás seguro de que quieres cambiar la propiedad \"{propertyName}\" {customText}? Esto puede afectar a los valores en {numOfCards} tarjeta(s) en este tablero, lo que puede resultar en una pérdida de datos.",
|
||||||
|
"CardDetailProperty.confirm-property-type-change": "Confirmar cambio de tipo de la propiedad",
|
||||||
|
"CardDetailProperty.delete-action-button": "Eliminar",
|
||||||
|
"CardDetailProperty.property-change-action-button": "Modificar propiedad",
|
||||||
|
"CardDetailProperty.property-changed": "¡Propiedad modificada exitosamente!",
|
||||||
|
"CardDetailProperty.property-deleted": "¡La propiedad {propertyName} ha sido eliminada exitosamente!",
|
||||||
|
"CardDetial.limited-link": "Aprende más sobre nuestros planes.",
|
||||||
|
"CardDialog.delete-confirmation-dialog-attachment": "Confirmar eliminación del archivo adjunto",
|
||||||
|
"CardDialog.delete-confirmation-dialog-button-text": "Eliminar",
|
||||||
|
"CardDialog.delete-confirmation-dialog-heading": "Confirmar eliminación de la tarjeta",
|
||||||
"CardDialog.editing-template": "Estás editando una plantilla.",
|
"CardDialog.editing-template": "Estás editando una plantilla.",
|
||||||
"CardDialog.nocard": "Esta tarjeta no existe o es inaccesible.",
|
"CardDialog.nocard": "Esta tarjeta no existe o es inaccesible.",
|
||||||
|
"Categories.CreateCategoryDialog.CancelText": "Cancelar",
|
||||||
|
"Categories.CreateCategoryDialog.CreateText": "Crear",
|
||||||
|
"Categories.CreateCategoryDialog.Placeholder": "Pon nombre a la categoría",
|
||||||
|
"Categories.CreateCategoryDialog.UpdateText": "Actualizar",
|
||||||
|
"CenterPanel.Login": "Ingresar",
|
||||||
|
"CenterPanel.Share": "Compartir",
|
||||||
|
"ChannelIntro.CreateBoard": "Crear un tablero",
|
||||||
"ColorOption.selectColor": "Seleccionar {color} Color",
|
"ColorOption.selectColor": "Seleccionar {color} Color",
|
||||||
"Comment.delete": "Borrar",
|
"Comment.delete": "Borrar",
|
||||||
"CommentsList.send": "Enviar",
|
"CommentsList.send": "Enviar",
|
||||||
|
@ -1,14 +1,42 @@
|
|||||||
{
|
{
|
||||||
|
"AppBar.Tooltip": "Veksle lenkede tavler",
|
||||||
|
"Attachment.Attachment-title": "Vedlegg",
|
||||||
|
"AttachmentBlock.DeleteAction": "slett",
|
||||||
|
"AttachmentBlock.addElement": "legg til {type}",
|
||||||
|
"AttachmentBlock.delete": "Vedlegg slettet.",
|
||||||
|
"AttachmentBlock.failed": "Denne filen kunne ikke lastes opp fordi størrelsesgrensen er nådd.",
|
||||||
|
"AttachmentBlock.upload": "Vedlegg lastes opp.",
|
||||||
|
"AttachmentBlock.uploadSuccess": "Vedlegg lastet opp.",
|
||||||
|
"AttachmentElement.delete-confirmation-dialog-button-text": "Slett",
|
||||||
|
"AttachmentElement.download": "Last ned",
|
||||||
|
"AttachmentElement.upload-percentage": "Laster opp ...({uploadPercent}%)",
|
||||||
"BoardComponent.add-a-group": "+ Legg til gruppe",
|
"BoardComponent.add-a-group": "+ Legg til gruppe",
|
||||||
"BoardComponent.delete": "Slett",
|
"BoardComponent.delete": "Slett",
|
||||||
"BoardComponent.hidden-columns": "Skjulte kolonner",
|
"BoardComponent.hidden-columns": "Skjulte kolonner",
|
||||||
"BoardComponent.hide": "Skjul",
|
"BoardComponent.hide": "Skjul",
|
||||||
"BoardComponent.new": "+ Ny",
|
"BoardComponent.new": "+ Ny",
|
||||||
"BoardComponent.no-property": "Ingen {property}",
|
"BoardComponent.no-property": "Ingen {property}",
|
||||||
"BoardComponent.no-property-title": "Elementer med en tom {property} område kommer hit. Denne kolonnen kan ikke fjernes.",
|
"BoardComponent.no-property-title": "Elementer med tom {property} atributt legges her. Denne kolonnen kan ikke fjernes.",
|
||||||
"BoardComponent.show": "Vis",
|
"BoardComponent.show": "Vis",
|
||||||
|
"BoardMember.schemeAdmin": "Admin",
|
||||||
|
"BoardMember.schemeCommenter": "Kommentator",
|
||||||
|
"BoardMember.schemeEditor": "Redaktør",
|
||||||
|
"BoardMember.schemeNone": "Ingen",
|
||||||
|
"BoardMember.schemeViewer": "Viser",
|
||||||
|
"BoardMember.unlinkChannel": "Fjern lenke",
|
||||||
"BoardPage.newVersion": "En ny versjon av Boards er tilgjengelig, klikk her for å laste inn på nytt.",
|
"BoardPage.newVersion": "En ny versjon av Boards er tilgjengelig, klikk her for å laste inn på nytt.",
|
||||||
"BoardPage.syncFailed": "Tavle kan slettes eller adgangen trekkes tilbake.",
|
"BoardPage.syncFailed": "Tavle kan slettes eller adgangen trekkes tilbake.",
|
||||||
|
"BoardTemplateSelector.add-template": "Lag ny mal",
|
||||||
|
"BoardTemplateSelector.create-empty-board": "Opprett tom tavle",
|
||||||
|
"BoardTemplateSelector.delete-template": "Slett",
|
||||||
|
"BoardTemplateSelector.description": "Legg til en tavle til sidestolpen med hvilken mal du vil fra listen under, eller start med en helt tom tavle.",
|
||||||
|
"BoardTemplateSelector.edit-template": "Rediger",
|
||||||
|
"BoardTemplateSelector.plugin.no-content-description": "Legg til en tavle i sidestolpen med hvilken mal du vil, eller start med en tom tavle.",
|
||||||
|
"BoardTemplateSelector.plugin.no-content-title": "Lag ny tavle",
|
||||||
|
"BoardTemplateSelector.title": "Lag ny tavle",
|
||||||
|
"BoardTemplateSelector.use-this-template": "Bruk denne malen",
|
||||||
|
"BoardsSwitcher.Title": "Finn tavle",
|
||||||
|
"BoardsUnfurl.Limited": "Flere detaljer er skjult fordi kortet er arkivert",
|
||||||
"BoardsUnfurl.Remainder": "+{remainder} mer",
|
"BoardsUnfurl.Remainder": "+{remainder} mer",
|
||||||
"BoardsUnfurl.Updated": "Oppdatert {time}",
|
"BoardsUnfurl.Updated": "Oppdatert {time}",
|
||||||
"Calculations.Options.average.displayName": "Gjennomsnitt",
|
"Calculations.Options.average.displayName": "Gjennomsnitt",
|
||||||
@ -16,11 +44,142 @@
|
|||||||
"Calculations.Options.count.displayName": "Antall",
|
"Calculations.Options.count.displayName": "Antall",
|
||||||
"Calculations.Options.count.label": "Antall",
|
"Calculations.Options.count.label": "Antall",
|
||||||
"Calculations.Options.countChecked.displayName": "Avkrysset",
|
"Calculations.Options.countChecked.displayName": "Avkrysset",
|
||||||
"Calculations.Options.countChecked.label": "Antall avsjekket",
|
"Calculations.Options.countChecked.label": "Antall valgt",
|
||||||
"Calculations.Options.countUnchecked.displayName": "Ikke avmerket",
|
"Calculations.Options.countUnchecked.displayName": "Ikke avmerket",
|
||||||
"Calculations.Options.countUnchecked.label": "Antall Ikke Avmerket",
|
"Calculations.Options.countUnchecked.label": "Antall ikke valgt",
|
||||||
"Calculations.Options.countUniqueValue.displayName": "Unik",
|
"Calculations.Options.countUniqueValue.displayName": "Unik",
|
||||||
"Calculations.Options.countUniqueValue.label": "Antall Unike Verdier",
|
"Calculations.Options.countUniqueValue.label": "Antall unike verdier",
|
||||||
"Calculations.Options.countValue.displayName": "Verdier",
|
"Calculations.Options.countValue.displayName": "Verdier",
|
||||||
"Calculations.Options.countValue.label": "Antall Verdier"
|
"Calculations.Options.countValue.label": "Antall verdier",
|
||||||
|
"Calculations.Options.dateRange.displayName": "Tidsrom",
|
||||||
|
"Calculations.Options.dateRange.label": "Tidsrom",
|
||||||
|
"Calculations.Options.earliest.displayName": "Tiligst",
|
||||||
|
"Calculations.Options.earliest.label": "Tiligst",
|
||||||
|
"Calculations.Options.latest.displayName": "Senest",
|
||||||
|
"Calculations.Options.latest.label": "Senest",
|
||||||
|
"Calculations.Options.max.displayName": "Maks",
|
||||||
|
"Calculations.Options.max.label": "Maks",
|
||||||
|
"Calculations.Options.median.displayName": "Median",
|
||||||
|
"Calculations.Options.median.label": "Median",
|
||||||
|
"Calculations.Options.min.displayName": "Min",
|
||||||
|
"Calculations.Options.min.label": "Min",
|
||||||
|
"Calculations.Options.none.displayName": "Kalkulèr",
|
||||||
|
"Calculations.Options.none.label": "Ingen",
|
||||||
|
"Calculations.Options.percentChecked.displayName": "Valgt",
|
||||||
|
"Calculations.Options.percentChecked.label": "Prosent valgt",
|
||||||
|
"Calculations.Options.percentUnchecked.displayName": "Ikke valgt",
|
||||||
|
"Calculations.Options.percentUnchecked.label": "Prosent ikke valgt",
|
||||||
|
"Calculations.Options.range.displayName": "Tidsrom",
|
||||||
|
"Calculations.Options.range.label": "Tidsrom",
|
||||||
|
"Calculations.Options.sum.displayName": "Sum",
|
||||||
|
"Calculations.Options.sum.label": "Sum",
|
||||||
|
"CalendarCard.untitled": "Uten navn",
|
||||||
|
"CardActionsMenu.copiedLink": "Kopiert!",
|
||||||
|
"CardActionsMenu.copyLink": "Kopier lenke",
|
||||||
|
"CardActionsMenu.delete": "Slett",
|
||||||
|
"CardActionsMenu.duplicate": "Dupliser",
|
||||||
|
"CardBadges.title-checkboxes": "Avkrysningsbokser",
|
||||||
|
"CardBadges.title-comments": "Kommentarer",
|
||||||
|
"CardBadges.title-description": "Dette kortet har en beskrivelsestekst",
|
||||||
|
"CardDetail.Attach": "Legg ved",
|
||||||
|
"CardDetail.Follow": "Følg",
|
||||||
|
"CardDetail.Following": "Følger",
|
||||||
|
"CardDetail.add-content": "Legg til innhold",
|
||||||
|
"CardDetail.add-icon": "Legg til ikon",
|
||||||
|
"CardDetail.add-property": "+ Legg til en verdi",
|
||||||
|
"CardDetail.addCardText": "legg inn tekst i kortet",
|
||||||
|
"CardDetail.limited-body": "Oppgrader til vår profesjonelle eller bedriftsplan.",
|
||||||
|
"CardDetail.limited-button": "Oppgrader",
|
||||||
|
"CardDetail.limited-title": "Dette kortet er skjult",
|
||||||
|
"CardDetail.moveContent": "Flytt innholdet",
|
||||||
|
"CardDetail.new-comment-placeholder": "Legg til kommentar ...",
|
||||||
|
"CardDetailProperty.confirm-delete-heading": "Bekreft sletting av verdi",
|
||||||
|
"CardDetailProperty.confirm-delete-subtext": "Er du sikker på at du vil slette verdien \"{propertyName}\"? Dette vil fjerne verdien fra alle kortene på denne tavlen.",
|
||||||
|
"CardDetailProperty.confirm-property-name-change-subtext": "Er du sikker på at du vil endre verdien \"{propertyName}\" {customText}? Dette vil påvirke verdien på {numOfCards} kort på denne tavlen, og kan forårsake at du mister informasjon.",
|
||||||
|
"CardDetailProperty.confirm-property-type-change": "Bekreft endring av verditype",
|
||||||
|
"CardDetailProperty.delete-action-button": "Slett",
|
||||||
|
"CardDetailProperty.property-change-action-button": "Endre verdi",
|
||||||
|
"CardDetailProperty.property-changed": "Verdi endret!",
|
||||||
|
"CardDetailProperty.property-deleted": "Fjernet {propertyName}!",
|
||||||
|
"CardDetailProperty.property-name-change-subtext": "type fra \"{oldPropType}\" til \"{newPropType}\"",
|
||||||
|
"CardDetial.limited-link": "Lær mer om våre planer.",
|
||||||
|
"CardDialog.delete-confirmation-dialog-attachment": "Bekreft sletting av vedlegg",
|
||||||
|
"CardDialog.delete-confirmation-dialog-button-text": "Slett",
|
||||||
|
"CardDialog.delete-confirmation-dialog-heading": "Bekreft sletting av kort",
|
||||||
|
"CardDialog.editing-template": "Du redigerer en mal.",
|
||||||
|
"CardDialog.nocard": "Dette kortet eksisterer ikke eller du har ikke tilgang.",
|
||||||
|
"Categories.CreateCategoryDialog.CancelText": "Avbryt",
|
||||||
|
"Categories.CreateCategoryDialog.CreateText": "Opprett",
|
||||||
|
"Categories.CreateCategoryDialog.Placeholder": "Navngi kategorien",
|
||||||
|
"Categories.CreateCategoryDialog.UpdateText": "Oppdater",
|
||||||
|
"CenterPanel.Login": "Logg inn",
|
||||||
|
"CenterPanel.Share": "Del",
|
||||||
|
"ChannelIntro.CreateBoard": "Opprett tavle",
|
||||||
|
"CloudMessage.cloud-server": "Få din egen gratis skytjener.",
|
||||||
|
"ColorOption.selectColor": "Velg {color} farge",
|
||||||
|
"Comment.delete": "Slett",
|
||||||
|
"CommentsList.send": "Send",
|
||||||
|
"ConfirmPerson.empty": "Tom",
|
||||||
|
"ConfirmPerson.search": "Søk ...",
|
||||||
|
"ConfirmationDialog.cancel-action": "Avbryt",
|
||||||
|
"ConfirmationDialog.confirm-action": "Bekreft",
|
||||||
|
"ContentBlock.Delete": "Slett",
|
||||||
|
"ContentBlock.DeleteAction": "slett",
|
||||||
|
"ContentBlock.addElement": "legg til {type}",
|
||||||
|
"ContentBlock.checkbox": "avkrysningsboks",
|
||||||
|
"ContentBlock.divider": "avdeler",
|
||||||
|
"ContentBlock.editCardCheckbox": "krysset-boks",
|
||||||
|
"ContentBlock.editCardCheckboxText": "rediger kort tekst",
|
||||||
|
"ContentBlock.editCardText": "rediger kort tekst",
|
||||||
|
"ContentBlock.editText": "Rediger tekst ...",
|
||||||
|
"ContentBlock.image": "bilde",
|
||||||
|
"ContentBlock.insertAbove": "Sett inn over",
|
||||||
|
"ContentBlock.moveBlock": "flytt kort innhold",
|
||||||
|
"ContentBlock.moveDown": "Flytt ned",
|
||||||
|
"ContentBlock.moveUp": "Flytt opp",
|
||||||
|
"ContentBlock.text": "tekst",
|
||||||
|
"DateRange.clear": "Tøm",
|
||||||
|
"DateRange.empty": "Tom",
|
||||||
|
"DateRange.endDate": "Sluttdato",
|
||||||
|
"DateRange.today": "I dag",
|
||||||
|
"DeleteBoardDialog.confirm-cancel": "Avbryt",
|
||||||
|
"DeleteBoardDialog.confirm-delete": "Slett",
|
||||||
|
"DeleteBoardDialog.confirm-info": "Er du sikker på at du vil slette tavlen \"{boardTitle}\"? Dette vil slette alle kortene på tavlen.",
|
||||||
|
"DeleteBoardDialog.confirm-info-template": "Er du sikker på at du vil slette tavlemalen \"{boardTitle}\"?",
|
||||||
|
"DeleteBoardDialog.confirm-tite": "Bekreft sletting av tavle",
|
||||||
|
"DeleteBoardDialog.confirm-tite-template": "Bekreft sletting av tavlemal",
|
||||||
|
"Dialog.closeDialog": "Lukk",
|
||||||
|
"EditableDayPicker.today": "I dag",
|
||||||
|
"Error.mobileweb": "Støtte for bruk i nettleser på mobil er i tidlig beta. Alt vil ikke fungere.",
|
||||||
|
"Error.websocket-closed": "Problemer med kobling til tjeneren. Sjekk konfigurasjonen hvis problemet vedvarer.",
|
||||||
|
"Filter.contains": "inneholder",
|
||||||
|
"Filter.ends-with": "ender med",
|
||||||
|
"Filter.includes": "inkluderer",
|
||||||
|
"Filter.is": "er",
|
||||||
|
"Filter.is-empty": "er tom",
|
||||||
|
"Filter.is-not-empty": "er ikke tom",
|
||||||
|
"Filter.is-not-set": "er ikke satt",
|
||||||
|
"Filter.is-set": "er satt",
|
||||||
|
"Filter.not-contains": "inkluderer ikke",
|
||||||
|
"Filter.not-ends-with": "ender ikke med",
|
||||||
|
"Filter.not-includes": "inkluderer ikke",
|
||||||
|
"Filter.not-starts-with": "starter ikke med",
|
||||||
|
"Filter.starts-with": "starter med",
|
||||||
|
"FilterByText.placeholder": "filtrer tekst",
|
||||||
|
"FilterComponent.add-filter": "+ Nytt filter",
|
||||||
|
"FilterComponent.delete": "Slett",
|
||||||
|
"FilterValue.empty": "(tom)",
|
||||||
|
"FindBoardsDialog.IntroText": "Søk etter tavle",
|
||||||
|
"FindBoardsDialog.NoResultsFor": "Ingen resultat for \"{searchQuery}\"",
|
||||||
|
"FindBoardsDialog.NoResultsSubtext": "Sjekk stavingen eller søk på noe annet.",
|
||||||
|
"FindBoardsDialog.SubTitle": "Skriv for å finne en tavle. Bruk <b>opp/ned</b> for å navigere. <b>Enter</b> for å velge, eller <b>Esc</b> for å avbryte",
|
||||||
|
"FindBoardsDialog.Title": "Finn tavle",
|
||||||
|
"GroupBy.hideEmptyGroups": "Skjul {count} tomme grupper",
|
||||||
|
"GroupBy.showHiddenGroups": "Vis {count} tomme grupper",
|
||||||
|
"GroupBy.ungroup": "Fjern fra gruppe",
|
||||||
|
"HideBoard.MenuOption": "Skjul tavlen",
|
||||||
|
"KanbanCard.untitled": "Uten navn",
|
||||||
|
"Mutator.new-board-from-template": "ny tavle fra mal",
|
||||||
|
"Mutator.new-card-from-template": "nytt kort fra mal",
|
||||||
|
"Mutator.new-template-from-card": "ny mal fra kort"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
{
|
{
|
||||||
"AppBar.Tooltip": "Ativar Boards vinculados",
|
"AppBar.Tooltip": "Ativar boards vinculados",
|
||||||
|
"Attachment.Attachment-title": "Anexo",
|
||||||
|
"AttachmentBlock.DeleteAction": "apagar",
|
||||||
|
"AttachmentBlock.addElement": "adicionar {type}",
|
||||||
|
"AttachmentBlock.delete": "Anexo apagado.",
|
||||||
|
"AttachmentBlock.uploadSuccess": "Anexo enviado.",
|
||||||
|
"AttachmentElement.delete-confirmation-dialog-button-text": "Apagar",
|
||||||
|
"AttachmentElement.upload-percentage": "Enviando...({uploadPercent}%)",
|
||||||
"BoardComponent.add-a-group": "+ Adicione um grupo",
|
"BoardComponent.add-a-group": "+ Adicione um grupo",
|
||||||
"BoardComponent.delete": "Excluir",
|
"BoardComponent.delete": "Excluir",
|
||||||
"BoardComponent.hidden-columns": "Colunas ocultas",
|
"BoardComponent.hidden-columns": "Colunas ocultas",
|
||||||
@ -16,8 +23,8 @@
|
|||||||
"BoardMember.unlinkChannel": "Desvincular",
|
"BoardMember.unlinkChannel": "Desvincular",
|
||||||
"BoardPage.newVersion": "Uma nova versão do Boards está disponível, clique aqui para recarregar.",
|
"BoardPage.newVersion": "Uma nova versão do Boards está disponível, clique aqui para recarregar.",
|
||||||
"BoardPage.syncFailed": "O Board pode ter sido excluído ou o acesso revogado.",
|
"BoardPage.syncFailed": "O Board pode ter sido excluído ou o acesso revogado.",
|
||||||
"BoardTemplateSelector.add-template": "Novo modelo",
|
"BoardTemplateSelector.add-template": "Criar novo modelo",
|
||||||
"BoardTemplateSelector.create-empty-board": "Criar board vazio",
|
"BoardTemplateSelector.create-empty-board": "Criar um board vazio",
|
||||||
"BoardTemplateSelector.delete-template": "Excluir",
|
"BoardTemplateSelector.delete-template": "Excluir",
|
||||||
"BoardTemplateSelector.description": "Adicione um quadro à barra lateral usando qualquer um dos modelos definidos abaixo ou comece do zero.",
|
"BoardTemplateSelector.description": "Adicione um quadro à barra lateral usando qualquer um dos modelos definidos abaixo ou comece do zero.",
|
||||||
"BoardTemplateSelector.edit-template": "Editar",
|
"BoardTemplateSelector.edit-template": "Editar",
|
||||||
@ -25,7 +32,7 @@
|
|||||||
"BoardTemplateSelector.plugin.no-content-title": "Criar um board",
|
"BoardTemplateSelector.plugin.no-content-title": "Criar um board",
|
||||||
"BoardTemplateSelector.title": "Criar um board",
|
"BoardTemplateSelector.title": "Criar um board",
|
||||||
"BoardTemplateSelector.use-this-template": "Use este template",
|
"BoardTemplateSelector.use-this-template": "Use este template",
|
||||||
"BoardsSwitcher.Title": "Encontrar Boards",
|
"BoardsSwitcher.Title": "Encontrar boards",
|
||||||
"BoardsUnfurl.Limited": "Detalhes adicionais estão ocultos devido ao cartão ter sido arquivado",
|
"BoardsUnfurl.Limited": "Detalhes adicionais estão ocultos devido ao cartão ter sido arquivado",
|
||||||
"BoardsUnfurl.Remainder": "+{remainder} mais",
|
"BoardsUnfurl.Remainder": "+{remainder} mais",
|
||||||
"BoardsUnfurl.Updated": "Atualizado {time}",
|
"BoardsUnfurl.Updated": "Atualizado {time}",
|
||||||
@ -71,6 +78,7 @@
|
|||||||
"CardBadges.title-checkboxes": "Caixa de seleção",
|
"CardBadges.title-checkboxes": "Caixa de seleção",
|
||||||
"CardBadges.title-comments": "Comentários",
|
"CardBadges.title-comments": "Comentários",
|
||||||
"CardBadges.title-description": "Este cartão tem uma descrição",
|
"CardBadges.title-description": "Este cartão tem uma descrição",
|
||||||
|
"CardDetail.Attach": "Anexar",
|
||||||
"CardDetail.Follow": "Seguir",
|
"CardDetail.Follow": "Seguir",
|
||||||
"CardDetail.Following": "Seguindo",
|
"CardDetail.Following": "Seguindo",
|
||||||
"CardDetail.add-content": "Adicionar conteúdo",
|
"CardDetail.add-content": "Adicionar conteúdo",
|
||||||
@ -102,10 +110,13 @@
|
|||||||
"Categories.CreateCategoryDialog.UpdateText": "Atualizar",
|
"Categories.CreateCategoryDialog.UpdateText": "Atualizar",
|
||||||
"CenterPanel.Login": "Login",
|
"CenterPanel.Login": "Login",
|
||||||
"CenterPanel.Share": "Compartilhar",
|
"CenterPanel.Share": "Compartilhar",
|
||||||
|
"ChannelIntro.CreateBoard": "Criar um board",
|
||||||
"CloudMessage.cloud-server": "Obtenha seu próprio cloud server de graça.",
|
"CloudMessage.cloud-server": "Obtenha seu próprio cloud server de graça.",
|
||||||
"ColorOption.selectColor": "Selecione {color} Cor",
|
"ColorOption.selectColor": "Selecione {color} Cor",
|
||||||
"Comment.delete": "Excluir",
|
"Comment.delete": "Excluir",
|
||||||
"CommentsList.send": "Enviar",
|
"CommentsList.send": "Enviar",
|
||||||
|
"ConfirmPerson.empty": "Vazio",
|
||||||
|
"ConfirmPerson.search": "Buscar...",
|
||||||
"ConfirmationDialog.cancel-action": "Cancelar",
|
"ConfirmationDialog.cancel-action": "Cancelar",
|
||||||
"ConfirmationDialog.confirm-action": "Confirmar",
|
"ConfirmationDialog.confirm-action": "Confirmar",
|
||||||
"ContentBlock.Delete": "Excluir",
|
"ContentBlock.Delete": "Excluir",
|
||||||
@ -181,6 +192,7 @@
|
|||||||
"OnboardingTour.ShareBoard.Body": "Você pode compartilhar seu board internament, com seu time, ou public para permitir visibilidade fora da sua organização.",
|
"OnboardingTour.ShareBoard.Body": "Você pode compartilhar seu board internament, com seu time, ou public para permitir visibilidade fora da sua organização.",
|
||||||
"OnboardingTour.ShareBoard.Title": "Compartilhar quadro",
|
"OnboardingTour.ShareBoard.Title": "Compartilhar quadro",
|
||||||
"PersonProperty.board-members": "Membros do Board",
|
"PersonProperty.board-members": "Membros do Board",
|
||||||
|
"PersonProperty.me": "Eu",
|
||||||
"PersonProperty.non-board-members": "Não membros do board",
|
"PersonProperty.non-board-members": "Não membros do board",
|
||||||
"PropertyMenu.Delete": "Excluir",
|
"PropertyMenu.Delete": "Excluir",
|
||||||
"PropertyMenu.changeType": "Alterar tipo da propriedade",
|
"PropertyMenu.changeType": "Alterar tipo da propriedade",
|
||||||
@ -235,6 +247,7 @@
|
|||||||
"Sidebar.import-archive": "Importar arquivo",
|
"Sidebar.import-archive": "Importar arquivo",
|
||||||
"Sidebar.invite-users": "Convidar usuários",
|
"Sidebar.invite-users": "Convidar usuários",
|
||||||
"Sidebar.logout": "Sair",
|
"Sidebar.logout": "Sair",
|
||||||
|
"Sidebar.new-category.badge": "Novo",
|
||||||
"Sidebar.no-boards-in-category": "Nenhum board",
|
"Sidebar.no-boards-in-category": "Nenhum board",
|
||||||
"Sidebar.product-tour": "Tour pelo produto",
|
"Sidebar.product-tour": "Tour pelo produto",
|
||||||
"Sidebar.random-icons": "Ícones aleatórios",
|
"Sidebar.random-icons": "Ícones aleatórios",
|
||||||
@ -257,6 +270,7 @@
|
|||||||
"SidebarTour.SidebarCategories.Body": "Todos seus boards agora são organizados sob sua nova barra lateral. Não é mais necessárioa alternar entre espaços de trabalho. Categorias personalizadas em suas estações prévias de trabalho foram automaticamente criadas para você como parte do seu upgrade para v7.2. Estas podem ser removidas ou editadas de acordo com a sua preferência.",
|
"SidebarTour.SidebarCategories.Body": "Todos seus boards agora são organizados sob sua nova barra lateral. Não é mais necessárioa alternar entre espaços de trabalho. Categorias personalizadas em suas estações prévias de trabalho foram automaticamente criadas para você como parte do seu upgrade para v7.2. Estas podem ser removidas ou editadas de acordo com a sua preferência.",
|
||||||
"SidebarTour.SidebarCategories.Link": "Saiba mais",
|
"SidebarTour.SidebarCategories.Link": "Saiba mais",
|
||||||
"SidebarTour.SidebarCategories.Title": "Categorias de barra lateral",
|
"SidebarTour.SidebarCategories.Title": "Categorias de barra lateral",
|
||||||
|
"SiteStats.total_boards": "Total de boards",
|
||||||
"TableComponent.add-icon": "Adicionar Ícone",
|
"TableComponent.add-icon": "Adicionar Ícone",
|
||||||
"TableComponent.name": "Nome",
|
"TableComponent.name": "Nome",
|
||||||
"TableComponent.plus-new": "+ Novo",
|
"TableComponent.plus-new": "+ Novo",
|
||||||
@ -267,6 +281,7 @@
|
|||||||
"TableHeaderMenu.insert-right": "Inserir à direita",
|
"TableHeaderMenu.insert-right": "Inserir à direita",
|
||||||
"TableHeaderMenu.sort-ascending": "Ordem ascendente",
|
"TableHeaderMenu.sort-ascending": "Ordem ascendente",
|
||||||
"TableHeaderMenu.sort-descending": "Ordem descendente",
|
"TableHeaderMenu.sort-descending": "Ordem descendente",
|
||||||
|
"TableRow.MoreOption": "Mais ações",
|
||||||
"TableRow.open": "Abrir",
|
"TableRow.open": "Abrir",
|
||||||
"TopBar.give-feedback": "Dar feedback",
|
"TopBar.give-feedback": "Dar feedback",
|
||||||
"URLProperty.copiedLink": "Copiado!",
|
"URLProperty.copiedLink": "Copiado!",
|
||||||
@ -348,6 +363,7 @@
|
|||||||
"calendar.month": "Mês",
|
"calendar.month": "Mês",
|
||||||
"calendar.today": "HOJE",
|
"calendar.today": "HOJE",
|
||||||
"calendar.week": "Semana",
|
"calendar.week": "Semana",
|
||||||
|
"centerPanel.unknown-user": "Usuário desconhecido",
|
||||||
"cloudMessage.learn-more": "Saiba mais",
|
"cloudMessage.learn-more": "Saiba mais",
|
||||||
"createImageBlock.failed": "Não foi possível enviar o arquivo. Limite de tamanho alcançado.",
|
"createImageBlock.failed": "Não foi possível enviar o arquivo. Limite de tamanho alcançado.",
|
||||||
"default-properties.badges": "Comentários e descrição",
|
"default-properties.badges": "Comentários e descrição",
|
||||||
@ -369,6 +385,7 @@
|
|||||||
"login.log-in-button": "Entrar",
|
"login.log-in-button": "Entrar",
|
||||||
"login.log-in-title": "Entrar",
|
"login.log-in-title": "Entrar",
|
||||||
"login.register-button": "ou criar uma conta se você ainda não tiver uma",
|
"login.register-button": "ou criar uma conta se você ainda não tiver uma",
|
||||||
|
"new_channel_modal.create_board.select_template_placeholder": "Selecionar um modelo",
|
||||||
"notification-box-card-limit-reached.close-tooltip": "Soneca por 10 dias",
|
"notification-box-card-limit-reached.close-tooltip": "Soneca por 10 dias",
|
||||||
"notification-box-card-limit-reached.contact-link": "notificar seu admin",
|
"notification-box-card-limit-reached.contact-link": "notificar seu admin",
|
||||||
"notification-box-card-limit-reached.link": "Atualizar para um plano pago",
|
"notification-box-card-limit-reached.link": "Atualizar para um plano pago",
|
||||||
@ -388,14 +405,14 @@
|
|||||||
"rhs-boards.dm": "DM",
|
"rhs-boards.dm": "DM",
|
||||||
"rhs-boards.gm": "GM",
|
"rhs-boards.gm": "GM",
|
||||||
"rhs-boards.header.dm": "esta Direct Message",
|
"rhs-boards.header.dm": "esta Direct Message",
|
||||||
"rhs-boards.header.gm": "Este Gruop Message",
|
"rhs-boards.header.gm": "esta mensagem de grupo",
|
||||||
"rhs-boards.last-update-at": "Última atualização em: {datetime}",
|
"rhs-boards.last-update-at": "Última atualização em: {datetime}",
|
||||||
"rhs-boards.link-boards-to-channel": "Vincular boards para {channelName}",
|
"rhs-boards.link-boards-to-channel": "Vincular boards para {channelName}",
|
||||||
"rhs-boards.linked-boards": "Boards vinculados",
|
"rhs-boards.linked-boards": "Boards vinculados",
|
||||||
"rhs-boards.no-boards-linked-to-channel": "Nenhum board está vinculado a {channelName} ainda",
|
"rhs-boards.no-boards-linked-to-channel": "Nenhum board está vinculado a {channelName} ainda",
|
||||||
"rhs-boards.no-boards-linked-to-channel-description": "Boards é uma ferramenta de gerenciamento de projeto que ajuda a definir, organizar, rastrear e gerenciar o trabalho entre times, usando uma visualização de quadro estilo Kaban familiar.",
|
"rhs-boards.no-boards-linked-to-channel-description": "Boards é uma ferramenta de gerenciamento de projeto que ajuda a definir, organizar, rastrear e gerenciar o trabalho entre times, usando uma visualização de quadro estilo Kaban familiar.",
|
||||||
"rhs-boards.unlink-board": "Desvincular board",
|
"rhs-boards.unlink-board": "Desvincular board",
|
||||||
"rhs-boards.unlink-board1": "Desvincular board Hello",
|
"rhs-boards.unlink-board1": "Desvincular board",
|
||||||
"rhs-channel-boards-header.title": "Boards",
|
"rhs-channel-boards-header.title": "Boards",
|
||||||
"share-board.publish": "Publicar",
|
"share-board.publish": "Publicar",
|
||||||
"share-board.share": "Compartilhar",
|
"share-board.share": "Compartilhar",
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
"AppBar.Tooltip": "Переключить связанные доски",
|
"AppBar.Tooltip": "Переключить связанные доски",
|
||||||
"Attachment.Attachment-title": "Вложение",
|
"Attachment.Attachment-title": "Вложение",
|
||||||
"AttachmentBlock.DeleteAction": "Удалить",
|
"AttachmentBlock.DeleteAction": "Удалить",
|
||||||
"AttachmentBlock.addElement": "добавить",
|
"AttachmentBlock.addElement": "добавить {type}",
|
||||||
"AttachmentBlock.delete": "Вложение успешно удалено.",
|
"AttachmentBlock.delete": "Вложение удалено.",
|
||||||
"AttachmentBlock.failed": "Не удалось загрузить файл. Достигнут предел размера вложения.",
|
"AttachmentBlock.failed": "Не удалось загрузить файл, так как превышена квота на размер файла.",
|
||||||
"AttachmentBlock.upload": "Загрузка вложения.",
|
"AttachmentBlock.upload": "Загрузка вложения.",
|
||||||
"AttachmentBlock.uploadSuccess": "Вложение успешно загружено.",
|
"AttachmentBlock.uploadSuccess": "Вложение загружено.",
|
||||||
"AttachmentElement.delete-confirmation-dialog-button-text": "Удалить",
|
"AttachmentElement.delete-confirmation-dialog-button-text": "Удалить",
|
||||||
"AttachmentElement.download": "Скачать",
|
"AttachmentElement.download": "Скачать",
|
||||||
"AttachmentElement.upload-percentage": "Загрузка",
|
"AttachmentElement.upload-percentage": "Загрузка...({uploadPercent}%)",
|
||||||
"BoardComponent.add-a-group": "+ Добавить группу",
|
"BoardComponent.add-a-group": "+ Добавить группу",
|
||||||
"BoardComponent.delete": "Удалить",
|
"BoardComponent.delete": "Удалить",
|
||||||
"BoardComponent.hidden-columns": "Скрытые столбцы",
|
"BoardComponent.hidden-columns": "Скрытые столбцы",
|
||||||
@ -31,12 +31,12 @@
|
|||||||
"BoardTemplateSelector.delete-template": "Удалить",
|
"BoardTemplateSelector.delete-template": "Удалить",
|
||||||
"BoardTemplateSelector.description": "Добавьте доску на боковую панель, используя любой из шаблонов, описанных ниже, или начните с нуля.",
|
"BoardTemplateSelector.description": "Добавьте доску на боковую панель, используя любой из шаблонов, описанных ниже, или начните с нуля.",
|
||||||
"BoardTemplateSelector.edit-template": "Изменить",
|
"BoardTemplateSelector.edit-template": "Изменить",
|
||||||
"BoardTemplateSelector.plugin.no-content-description": "Добавьте доску на боковую панель, используя любой из указанных ниже шаблонов, или начните с нуля.{lineBreak} Участники \"{teamName}\" будут иметь доступ к созданным здесь доскам.",
|
"BoardTemplateSelector.plugin.no-content-description": "Добавьте доску на боковую панель, используя любой из указанных ниже шаблонов, или начните с нуля.",
|
||||||
"BoardTemplateSelector.plugin.no-content-title": "Создать доску",
|
"BoardTemplateSelector.plugin.no-content-title": "Создать доску",
|
||||||
"BoardTemplateSelector.title": "Создать доску",
|
"BoardTemplateSelector.title": "Создать доску",
|
||||||
"BoardTemplateSelector.use-this-template": "Использовать этот шаблон",
|
"BoardTemplateSelector.use-this-template": "Использовать этот шаблон",
|
||||||
"BoardsSwitcher.Title": "Найти доски",
|
"BoardsSwitcher.Title": "Найти доски",
|
||||||
"BoardsUnfurl.Limited": "Информация скрыта в связи с тем, что карточка находится в архиве",
|
"BoardsUnfurl.Limited": "Информация скрыта, потому что карточка находится в архиве",
|
||||||
"BoardsUnfurl.Remainder": "+{remainder} ещё",
|
"BoardsUnfurl.Remainder": "+{remainder} ещё",
|
||||||
"BoardsUnfurl.Updated": "Обновлено {time}",
|
"BoardsUnfurl.Updated": "Обновлено {time}",
|
||||||
"Calculations.Options.average.displayName": "Среднее",
|
"Calculations.Options.average.displayName": "Среднее",
|
||||||
@ -103,9 +103,9 @@
|
|||||||
"CardDetailProperty.property-deleted": "{propertyName} успешно удалено!",
|
"CardDetailProperty.property-deleted": "{propertyName} успешно удалено!",
|
||||||
"CardDetailProperty.property-name-change-subtext": "тип из \"{oldPropType}\" в \"{newPropType}\"",
|
"CardDetailProperty.property-name-change-subtext": "тип из \"{oldPropType}\" в \"{newPropType}\"",
|
||||||
"CardDetial.limited-link": "Узнайте больше о наших планах.",
|
"CardDetial.limited-link": "Узнайте больше о наших планах.",
|
||||||
"CardDialog.delete-confirmation-dialog-attachment": "Подтвердите удаление вложения!",
|
"CardDialog.delete-confirmation-dialog-attachment": "Подтвердите удаление вложения",
|
||||||
"CardDialog.delete-confirmation-dialog-button-text": "Удалить",
|
"CardDialog.delete-confirmation-dialog-button-text": "Удалить",
|
||||||
"CardDialog.delete-confirmation-dialog-heading": "Подтвердите удаление карточки!",
|
"CardDialog.delete-confirmation-dialog-heading": "Подтвердите удаление карточки",
|
||||||
"CardDialog.editing-template": "Вы редактируете шаблон.",
|
"CardDialog.editing-template": "Вы редактируете шаблон.",
|
||||||
"CardDialog.nocard": "Эта карточка не существует или недоступна.",
|
"CardDialog.nocard": "Эта карточка не существует или недоступна.",
|
||||||
"Categories.CreateCategoryDialog.CancelText": "Отмена",
|
"Categories.CreateCategoryDialog.CancelText": "Отмена",
|
||||||
@ -114,10 +114,13 @@
|
|||||||
"Categories.CreateCategoryDialog.UpdateText": "Обновить",
|
"Categories.CreateCategoryDialog.UpdateText": "Обновить",
|
||||||
"CenterPanel.Login": "Логин",
|
"CenterPanel.Login": "Логин",
|
||||||
"CenterPanel.Share": "Поделиться",
|
"CenterPanel.Share": "Поделиться",
|
||||||
|
"ChannelIntro.CreateBoard": "Создать доску",
|
||||||
"CloudMessage.cloud-server": "Получите свой бесплатный облачный сервер.",
|
"CloudMessage.cloud-server": "Получите свой бесплатный облачный сервер.",
|
||||||
"ColorOption.selectColor": "Выберите цвет {color}",
|
"ColorOption.selectColor": "Выберите цвет {color}",
|
||||||
"Comment.delete": "Удалить",
|
"Comment.delete": "Удалить",
|
||||||
"CommentsList.send": "Отправить",
|
"CommentsList.send": "Отправить",
|
||||||
|
"ConfirmPerson.empty": "Пусто",
|
||||||
|
"ConfirmPerson.search": "Поиск...",
|
||||||
"ConfirmationDialog.cancel-action": "Отмена",
|
"ConfirmationDialog.cancel-action": "Отмена",
|
||||||
"ConfirmationDialog.confirm-action": "Подтвердить",
|
"ConfirmationDialog.confirm-action": "Подтвердить",
|
||||||
"ContentBlock.Delete": "Удалить",
|
"ContentBlock.Delete": "Удалить",
|
||||||
@ -165,6 +168,7 @@
|
|||||||
"FilterByText.placeholder": "фильтровать текст",
|
"FilterByText.placeholder": "фильтровать текст",
|
||||||
"FilterComponent.add-filter": "+ Добавить фильтр",
|
"FilterComponent.add-filter": "+ Добавить фильтр",
|
||||||
"FilterComponent.delete": "Удалить",
|
"FilterComponent.delete": "Удалить",
|
||||||
|
"FilterValue.empty": "(пусто)",
|
||||||
"FindBoardsDialog.IntroText": "Поиск досок",
|
"FindBoardsDialog.IntroText": "Поиск досок",
|
||||||
"FindBoardsDialog.NoResultsFor": "Нет результатов для \"{searchQuery}\"",
|
"FindBoardsDialog.NoResultsFor": "Нет результатов для \"{searchQuery}\"",
|
||||||
"FindBoardsDialog.NoResultsSubtext": "Проверьте правильность написания или попробуйте другой запрос.",
|
"FindBoardsDialog.NoResultsSubtext": "Проверьте правильность написания или попробуйте другой запрос.",
|
||||||
@ -183,7 +187,7 @@
|
|||||||
"OnboardingTour.AddComments.Title": "Добавить комментарии",
|
"OnboardingTour.AddComments.Title": "Добавить комментарии",
|
||||||
"OnboardingTour.AddDescription.Body": "Добавьте описание к своей карточке, чтобы Ваши коллеги по команде знали, о чем эта карточка.",
|
"OnboardingTour.AddDescription.Body": "Добавьте описание к своей карточке, чтобы Ваши коллеги по команде знали, о чем эта карточка.",
|
||||||
"OnboardingTour.AddDescription.Title": "Добавить описание",
|
"OnboardingTour.AddDescription.Title": "Добавить описание",
|
||||||
"OnboardingTour.AddProperties.Body": "Добавляйте различные свойства карточкам, чтобы сделать их более мощными!",
|
"OnboardingTour.AddProperties.Body": "Добавляйте различные свойства карточкам, чтобы сделать их более значительными.",
|
||||||
"OnboardingTour.AddProperties.Title": "Добавить свойства",
|
"OnboardingTour.AddProperties.Title": "Добавить свойства",
|
||||||
"OnboardingTour.AddView.Body": "Перейдите сюда, чтобы создать новый вид для организации доски с использованием различных макетов.",
|
"OnboardingTour.AddView.Body": "Перейдите сюда, чтобы создать новый вид для организации доски с использованием различных макетов.",
|
||||||
"OnboardingTour.AddView.Title": "Добавить новый вид",
|
"OnboardingTour.AddView.Title": "Добавить новый вид",
|
||||||
@ -284,6 +288,7 @@
|
|||||||
"TableHeaderMenu.insert-right": "Вставить справа",
|
"TableHeaderMenu.insert-right": "Вставить справа",
|
||||||
"TableHeaderMenu.sort-ascending": "Сортировать по возрастанию",
|
"TableHeaderMenu.sort-ascending": "Сортировать по возрастанию",
|
||||||
"TableHeaderMenu.sort-descending": "Сортировать по убыванию",
|
"TableHeaderMenu.sort-descending": "Сортировать по убыванию",
|
||||||
|
"TableRow.DuplicateCard": "дублировать карточку",
|
||||||
"TableRow.MoreOption": "Больше действий",
|
"TableRow.MoreOption": "Больше действий",
|
||||||
"TableRow.open": "Открыть",
|
"TableRow.open": "Открыть",
|
||||||
"TopBar.give-feedback": "Дать обратную связь",
|
"TopBar.give-feedback": "Дать обратную связь",
|
||||||
@ -351,10 +356,13 @@
|
|||||||
"WelcomePage.Explore.Button": "Исследовать",
|
"WelcomePage.Explore.Button": "Исследовать",
|
||||||
"WelcomePage.Heading": "Добро пожаловать на Доски",
|
"WelcomePage.Heading": "Добро пожаловать на Доски",
|
||||||
"WelcomePage.NoThanks.Text": "Нет спасибо, сам разберусь",
|
"WelcomePage.NoThanks.Text": "Нет спасибо, сам разберусь",
|
||||||
|
"WelcomePage.StartUsingIt.Text": "Начать пользоваться",
|
||||||
"Workspace.editing-board-template": "Вы редактируете шаблон доски.",
|
"Workspace.editing-board-template": "Вы редактируете шаблон доски.",
|
||||||
|
"badge.guest": "Гость",
|
||||||
"boardSelector.confirm-link-board": "Привязать доску к каналу",
|
"boardSelector.confirm-link-board": "Привязать доску к каналу",
|
||||||
"boardSelector.confirm-link-board-button": "Да, ссылка доски",
|
"boardSelector.confirm-link-board-button": "Да, ссылка доски",
|
||||||
"boardSelector.confirm-link-board-subtext": "Связывание доски \"{boardName}\" с этим каналом даст всем участникам этого канала доступ на редактирование доски. Вы уверены, что хотите связать это?",
|
"boardSelector.confirm-link-board-subtext": "Связывание доски \"{boardName}\" с каналом даст всем участникам канала доступ на редактирование доски. Вы можете в любое время отвязать доску о канала.",
|
||||||
|
"boardSelector.confirm-link-board-subtext-with-other-channel": "Привязка \"{boardName}\" с каналом приведет к возможности её редактирования всеми участниками канала (существующими и новыми). Кроме гостей канала.{lineBreak} Эта доска сейчас связана с другим каналом. Он будет отключен, если вы решите изменить привязку.",
|
||||||
"boardSelector.create-a-board": "Создать доску",
|
"boardSelector.create-a-board": "Создать доску",
|
||||||
"boardSelector.link": "Ссылка",
|
"boardSelector.link": "Ссылка",
|
||||||
"boardSelector.search-for-boards": "Поиск досок",
|
"boardSelector.search-for-boards": "Поиск досок",
|
||||||
@ -363,6 +371,8 @@
|
|||||||
"calendar.month": "Месяц",
|
"calendar.month": "Месяц",
|
||||||
"calendar.today": "СЕГОДНЯ",
|
"calendar.today": "СЕГОДНЯ",
|
||||||
"calendar.week": "Неделя",
|
"calendar.week": "Неделя",
|
||||||
|
"centerPanel.undefined": "Отсутствует {propertyName}",
|
||||||
|
"centerPanel.unknown-user": "Неизвестный пользователь",
|
||||||
"cloudMessage.learn-more": "Учить больше",
|
"cloudMessage.learn-more": "Учить больше",
|
||||||
"createImageBlock.failed": "Не удалось загрузить файл. Достигнут предел размера файла.",
|
"createImageBlock.failed": "Не удалось загрузить файл. Достигнут предел размера файла.",
|
||||||
"default-properties.badges": "Комментарии и описание",
|
"default-properties.badges": "Комментарии и описание",
|
||||||
@ -377,11 +387,15 @@
|
|||||||
"error.team-undefined": "Не корректная команда.",
|
"error.team-undefined": "Не корректная команда.",
|
||||||
"error.unknown": "Произошла ошибка.",
|
"error.unknown": "Произошла ошибка.",
|
||||||
"generic.previous": "Предыдущий",
|
"generic.previous": "Предыдущий",
|
||||||
"imagePaste.upload-failed": "Некоторые файлы не загружены. Достигнут предел размера файла",
|
"imagePaste.upload-failed": "Некоторые файлы не загружены из-за превышения квоты на размер файла.",
|
||||||
"limitedCard.title": "Карточки скрыты",
|
"limitedCard.title": "Карточки скрыты",
|
||||||
"login.log-in-button": "Вход в систему",
|
"login.log-in-button": "Вход в систему",
|
||||||
"login.log-in-title": "Вход в систему",
|
"login.log-in-title": "Вход в систему",
|
||||||
"login.register-button": "или создать аккаунт, если у Вас его нет",
|
"login.register-button": "или создать аккаунт, если у Вас его нет",
|
||||||
|
"new_channel_modal.create_board.empty_board_description": "Создать новую пустую доску",
|
||||||
|
"new_channel_modal.create_board.empty_board_title": "Пустая доска",
|
||||||
|
"new_channel_modal.create_board.select_template_placeholder": "Выбрать шаблон",
|
||||||
|
"new_channel_modal.create_board.title": "Создать доску для этого канала",
|
||||||
"notification-box-card-limit-reached.close-tooltip": "Отложить на 10 дней",
|
"notification-box-card-limit-reached.close-tooltip": "Отложить на 10 дней",
|
||||||
"notification-box-card-limit-reached.contact-link": "уведомить Вашего администратора",
|
"notification-box-card-limit-reached.contact-link": "уведомить Вашего администратора",
|
||||||
"notification-box-card-limit-reached.link": "Перейти на платный тариф",
|
"notification-box-card-limit-reached.link": "Перейти на платный тариф",
|
||||||
@ -389,13 +403,18 @@
|
|||||||
"notification-box-cards-hidden.title": "Это действие скрыло другую карточку",
|
"notification-box-cards-hidden.title": "Это действие скрыло другую карточку",
|
||||||
"notification-box.card-limit-reached.not-admin.text": "Чтобы получить доступ к архивным карточкам, Вы можете {contactLink} перейти на платный тариф.",
|
"notification-box.card-limit-reached.not-admin.text": "Чтобы получить доступ к архивным карточкам, Вы можете {contactLink} перейти на платный тариф.",
|
||||||
"notification-box.card-limit-reached.text": "Достигнут лимит карточки, чтобы просмотреть старые карточки, {link}",
|
"notification-box.card-limit-reached.text": "Достигнут лимит карточки, чтобы просмотреть старые карточки, {link}",
|
||||||
|
"person.add-user-to-board": "Добавить {username} на доску",
|
||||||
|
"person.add-user-to-board-confirm-button": "Добавить доску",
|
||||||
|
"person.add-user-to-board-permissions": "Разрешения",
|
||||||
|
"person.add-user-to-board-question": "Вы хотите добавить {username} на доску?",
|
||||||
"register.login-button": "или войти в систему, если у вас уже есть аккаунт",
|
"register.login-button": "или войти в систему, если у вас уже есть аккаунт",
|
||||||
"register.signup-title": "Зарегистрируйте свой аккаунт",
|
"register.signup-title": "Зарегистрируйте свой аккаунт",
|
||||||
|
"rhs-board-non-admin-msg": "Вы не являетесь администратором этой доски",
|
||||||
"rhs-boards.add": "Добавить",
|
"rhs-boards.add": "Добавить",
|
||||||
"rhs-boards.last-update-at": "Последнее обновление: {datetime}",
|
"rhs-boards.last-update-at": "Последнее обновление: {datetime}",
|
||||||
"rhs-boards.link-boards-to-channel": "Связать доски с {channelName}",
|
"rhs-boards.link-boards-to-channel": "Связать доски с {channelName}",
|
||||||
"rhs-boards.linked-boards": "Связанные доски",
|
"rhs-boards.linked-boards": "Связанные доски",
|
||||||
"rhs-boards.no-boards-linked-to-channel": "К каналу {channelName} пока не подключены доски.",
|
"rhs-boards.no-boards-linked-to-channel": "К каналу {channelName} пока не подключены доски",
|
||||||
"rhs-boards.no-boards-linked-to-channel-description": "Доски — это инструмент управления проектами, который помогает определять, организовывать, отслеживать и управлять работой между командами, используя знакомое представление доски Канбан.",
|
"rhs-boards.no-boards-linked-to-channel-description": "Доски — это инструмент управления проектами, который помогает определять, организовывать, отслеживать и управлять работой между командами, используя знакомое представление доски Канбан.",
|
||||||
"rhs-boards.unlink-board": "Отвязать доску",
|
"rhs-boards.unlink-board": "Отвязать доску",
|
||||||
"rhs-channel-boards-header.title": "Доски",
|
"rhs-channel-boards-header.title": "Доски",
|
||||||
|
@ -1,14 +1,40 @@
|
|||||||
{
|
{
|
||||||
|
"Attachment.Attachment-title": "Príloha",
|
||||||
|
"AttachmentBlock.DeleteAction": "odstrániť",
|
||||||
|
"AttachmentBlock.delete": "Príloha odstránená.",
|
||||||
|
"AttachmentBlock.failed": "Tento súbor nebol nahratý, pretože presiahol veľkostný limit.",
|
||||||
|
"AttachmentBlock.upload": "Príloha sa nahráva.",
|
||||||
|
"AttachmentBlock.uploadSuccess": "Príloha bola nahratá.",
|
||||||
|
"AttachmentElement.delete-confirmation-dialog-button-text": "Odstrániť",
|
||||||
|
"AttachmentElement.download": "Stiahnuť",
|
||||||
|
"AttachmentElement.upload-percentage": "Nahrávam... ({uploadPercent}%)",
|
||||||
"BoardComponent.add-a-group": "+ Pridaj skupinu",
|
"BoardComponent.add-a-group": "+ Pridaj skupinu",
|
||||||
"BoardComponent.delete": "Mazať",
|
"BoardComponent.delete": "Odstrániť",
|
||||||
"BoardComponent.hidden-columns": "Skryté stľpce",
|
"BoardComponent.hidden-columns": "Skryté stĺpce",
|
||||||
"BoardComponent.hide": "Skryť",
|
"BoardComponent.hide": "Skryť",
|
||||||
"BoardComponent.new": "+ Nový",
|
"BoardComponent.new": "+ Nový",
|
||||||
"BoardComponent.no-property": "žiadna {property}",
|
"BoardComponent.no-property": "žiadna {property}",
|
||||||
"BoardComponent.no-property-title": "Položky s prázdnou {property} pôjdu tu. Tento stĺpec nemožno vymazať.",
|
"BoardComponent.no-property-title": "Položky s prázdnou {property} pôjdu tu. Tento stĺpec nemožno vymazať.",
|
||||||
"BoardComponent.show": "Ukáž",
|
"BoardComponent.show": "Ukáž",
|
||||||
|
"BoardMember.schemeAdmin": "Administrátor",
|
||||||
|
"BoardMember.schemeCommenter": "Komentátor",
|
||||||
|
"BoardMember.schemeEditor": "Editor",
|
||||||
|
"BoardMember.schemeNone": "Žiadny",
|
||||||
|
"BoardMember.schemeViewer": "Sledovateľ",
|
||||||
|
"BoardMember.unlinkChannel": "Odpojiť",
|
||||||
"BoardPage.newVersion": "Nová verzia je dostupná, kliknite tu pre znovu načítanie.",
|
"BoardPage.newVersion": "Nová verzia je dostupná, kliknite tu pre znovu načítanie.",
|
||||||
"BoardPage.syncFailed": "Nástenka môže byť vymazaná, alebo prístup odobraný.",
|
"BoardPage.syncFailed": "Nástenka môže byť vymazaná alebo prístup odobraný.",
|
||||||
|
"BoardTemplateSelector.add-template": "Vytvoriť novú šablónu",
|
||||||
|
"BoardTemplateSelector.create-empty-board": "Vytvoriť prázdnu nástenku",
|
||||||
|
"BoardTemplateSelector.delete-template": "Odstrániť",
|
||||||
|
"BoardTemplateSelector.description": "Pridajte nástenku do bočného panelu pomocou ktorýchkoľvek šablón definovaných dole alebo začnite od začiatku.",
|
||||||
|
"BoardTemplateSelector.edit-template": "Upraviť",
|
||||||
|
"BoardTemplateSelector.plugin.no-content-description": "Pridajte nástenku do bočného panelu pomocou ktorýchkoľvek šablón dole alebo začnite od začiatku.",
|
||||||
|
"BoardTemplateSelector.plugin.no-content-title": "Vytvoriť nástenku",
|
||||||
|
"BoardTemplateSelector.title": "Vytvoriť nástenku",
|
||||||
|
"BoardTemplateSelector.use-this-template": "Použiť túto šablónu",
|
||||||
|
"BoardsSwitcher.Title": "Hľadať nástenky",
|
||||||
|
"BoardsUnfurl.Limited": "Ďalšie detaily sú skryté, pretože je karta archivovaná",
|
||||||
"BoardsUnfurl.Remainder": "+{remainder} viac",
|
"BoardsUnfurl.Remainder": "+{remainder} viac",
|
||||||
"BoardsUnfurl.Updated": "Upravené {time}",
|
"BoardsUnfurl.Updated": "Upravené {time}",
|
||||||
"Calculations.Options.average.displayName": "Priemer",
|
"Calculations.Options.average.displayName": "Priemer",
|
||||||
@ -16,13 +42,13 @@
|
|||||||
"Calculations.Options.count.displayName": "Počet",
|
"Calculations.Options.count.displayName": "Počet",
|
||||||
"Calculations.Options.count.label": "Počet",
|
"Calculations.Options.count.label": "Počet",
|
||||||
"Calculations.Options.countChecked.displayName": "Označené",
|
"Calculations.Options.countChecked.displayName": "Označené",
|
||||||
"Calculations.Options.countChecked.label": "Spočítaj označené",
|
"Calculations.Options.countChecked.label": "Počítať označené",
|
||||||
"Calculations.Options.countUnchecked.displayName": "Neoznačené",
|
"Calculations.Options.countUnchecked.displayName": "Neoznačené",
|
||||||
"Calculations.Options.countUnchecked.label": "Spočítaj neoznačené",
|
"Calculations.Options.countUnchecked.label": "Počítať neoznačené",
|
||||||
"Calculations.Options.countUniqueValue.displayName": "Unikátne",
|
"Calculations.Options.countUniqueValue.displayName": "Unikátne",
|
||||||
"Calculations.Options.countUniqueValue.label": "Spočítaj unikátne hodnoty",
|
"Calculations.Options.countUniqueValue.label": "Počítať unikátne hodnoty",
|
||||||
"Calculations.Options.countValue.displayName": "Hodnoty",
|
"Calculations.Options.countValue.displayName": "Hodnoty",
|
||||||
"Calculations.Options.countValue.label": "Spočítaj hodnoty",
|
"Calculations.Options.countValue.label": "Počítať hodnotu",
|
||||||
"Calculations.Options.dateRange.displayName": "Rozsah",
|
"Calculations.Options.dateRange.displayName": "Rozsah",
|
||||||
"Calculations.Options.dateRange.label": "Rozsah",
|
"Calculations.Options.dateRange.label": "Rozsah",
|
||||||
"Calculations.Options.earliest.displayName": "Prvý",
|
"Calculations.Options.earliest.displayName": "Prvý",
|
||||||
@ -31,76 +57,130 @@
|
|||||||
"Calculations.Options.latest.label": "Posledný",
|
"Calculations.Options.latest.label": "Posledný",
|
||||||
"Calculations.Options.max.displayName": "Max",
|
"Calculations.Options.max.displayName": "Max",
|
||||||
"Calculations.Options.max.label": "Max",
|
"Calculations.Options.max.label": "Max",
|
||||||
"Calculations.Options.median.displayName": "Median",
|
"Calculations.Options.median.displayName": "Medián",
|
||||||
"Calculations.Options.median.label": "Median",
|
"Calculations.Options.median.label": "Medián",
|
||||||
"Calculations.Options.min.displayName": "Min",
|
"Calculations.Options.min.displayName": "Min",
|
||||||
"Calculations.Options.min.label": "Min",
|
"Calculations.Options.min.label": "Min",
|
||||||
"Calculations.Options.none.displayName": "Vypočítaj",
|
"Calculations.Options.none.displayName": "Vypočítať",
|
||||||
"Calculations.Options.none.label": "Nič",
|
"Calculations.Options.none.label": "Nič",
|
||||||
"Calculations.Options.percentChecked.displayName": "Skontrolované",
|
"Calculations.Options.percentChecked.displayName": "Označené",
|
||||||
"Calculations.Options.percentChecked.label": "Percent Skontrolovaných",
|
"Calculations.Options.percentChecked.label": "Percent skontrolovaných",
|
||||||
"Calculations.Options.percentUnchecked.displayName": "Neskontrolované",
|
"Calculations.Options.percentUnchecked.displayName": "Neskontrolované",
|
||||||
"Calculations.Options.percentUnchecked.label": "Percent neskontrolovaných",
|
"Calculations.Options.percentUnchecked.label": "Percent neskontrolovaných",
|
||||||
"Calculations.Options.range.displayName": "Rozsah",
|
"Calculations.Options.range.displayName": "Rozsah",
|
||||||
"Calculations.Options.range.label": "Rozsah",
|
"Calculations.Options.range.label": "Rozsah",
|
||||||
"Calculations.Options.sum.displayName": "Súčet",
|
"Calculations.Options.sum.displayName": "Súčet",
|
||||||
"Calculations.Options.sum.label": "Súčet",
|
"Calculations.Options.sum.label": "Súčet",
|
||||||
"CardDetail.Follow": "Sleduj",
|
"CalendarCard.untitled": "Bez názvu",
|
||||||
"CardDetail.Following": "Sledujúce",
|
"CardActionsMenu.copiedLink": "Skopírované!",
|
||||||
"CardDetail.add-content": "Pridaj obsah",
|
"CardActionsMenu.copyLink": "Skopírovať odkaz",
|
||||||
"CardDetail.add-icon": "Pridaj ikonu",
|
"CardActionsMenu.delete": "Odstrániť",
|
||||||
"CardDetail.add-property": "+ Pridaj vlastnosť",
|
"CardActionsMenu.duplicate": "Duplikovať",
|
||||||
"CardDetail.addCardText": "Pridaj text karty",
|
"CardBadges.title-checkboxes": "Začiarkávacie políčka",
|
||||||
"CardDetail.moveContent": "presuň obsah karty",
|
"CardBadges.title-comments": "Komentáre",
|
||||||
"CardDetail.new-comment-placeholder": "Pridaj komentár ...",
|
"CardBadges.title-description": "Táto karta má popis",
|
||||||
"CardDetailProperty.confirm-delete-heading": "Potvrď vymazanie vlastnosti",
|
"CardDetail.Attach": "Priložiť",
|
||||||
"CardDetailProperty.confirm-delete-subtext": "Skutočne chcete vymazať \"{propertyName}\"? Mazaním ju odstránite zo všetkých kariet na tabuli.",
|
"CardDetail.Follow": "Sledovať",
|
||||||
"CardDetailProperty.confirm-property-name-change-subtext": "Skutočne chcete vymazať \"{propertyName}\" {customText}? Ovplyvní to hodnoty na {numOfCards} kartách na tabuli, a môže viesť k strate dát.",
|
"CardDetail.Following": "Sledujúci",
|
||||||
"CardDetailProperty.confirm-property-type-change": "Potvrď zmenu typu vlastnosti!",
|
"CardDetail.add-content": "Pridať obsah",
|
||||||
|
"CardDetail.add-icon": "Pridať ikonu",
|
||||||
|
"CardDetail.add-property": "+ Pridať vlastnosť",
|
||||||
|
"CardDetail.addCardText": "pridať text karty",
|
||||||
|
"CardDetail.limited-body": "Vylepšiť na náš Professional alebo Enterprise plán.",
|
||||||
|
"CardDetail.limited-button": "Zmeniť plán",
|
||||||
|
"CardDetail.limited-title": "Táto karta je skrytá",
|
||||||
|
"CardDetail.moveContent": "Presunúť obsah karty",
|
||||||
|
"CardDetail.new-comment-placeholder": "Pridať komentár...",
|
||||||
|
"CardDetailProperty.confirm-delete-heading": "Potvrdiť vymazanie vlastnosti",
|
||||||
|
"CardDetailProperty.confirm-delete-subtext": "Skutočne chcete vymazať hodnotu \"{propertyName}\"? Bude odstránená zo všetkých kariet na tejto tabuli.",
|
||||||
|
"CardDetailProperty.confirm-property-name-change-subtext": "Skutočne chcete zmeniť hodnotu \"{propertyName}\" {customText}? Ovplyvní to {numOfCards} kariet na tabuli a môže viesť k strate dát.",
|
||||||
|
"CardDetailProperty.confirm-property-type-change": "Potvrdiť zmenu typu vlastnosti",
|
||||||
"CardDetailProperty.delete-action-button": "Odstrániť",
|
"CardDetailProperty.delete-action-button": "Odstrániť",
|
||||||
"CardDetailProperty.property-change-action-button": "Zmeniť vlastnosť",
|
"CardDetailProperty.property-change-action-button": "Zmeniť vlastnosť",
|
||||||
"CardDetailProperty.property-changed": "Zmena vlastnosti úspešná!",
|
"CardDetailProperty.property-changed": "Zmena vlastnosti úspešná!",
|
||||||
"CardDetailProperty.property-deleted": "Mazanie {propertyName} úspešné!",
|
"CardDetailProperty.property-deleted": "Odstránenie {propertyName} úspešné!",
|
||||||
"CardDetailProperty.property-name-change-subtext": "typ od \"{oldPropType}\" do \"{newPropType}\"",
|
"CardDetailProperty.property-name-change-subtext": "typ z \"{oldPropType}\" na \"{newPropType}\"",
|
||||||
"CardDialog.editing-template": "Editujete template.",
|
"CardDetial.limited-link": "Dozvedieť sa viac o našich plánoch.",
|
||||||
"CardDialog.nocard": "Karta neexistuje alebo je neprístupná.",
|
"CardDialog.delete-confirmation-dialog-attachment": "Potvrdiť odstránenie prílohy",
|
||||||
"ColorOption.selectColor": "Vyber {color} farbu",
|
"CardDialog.delete-confirmation-dialog-button-text": "Odstrániť",
|
||||||
|
"CardDialog.delete-confirmation-dialog-heading": "Potvrdiť odstránenie karty",
|
||||||
|
"CardDialog.editing-template": "Upravujete šablónu.",
|
||||||
|
"CardDialog.nocard": "Táto karta neexistuje alebo nie je prístupná.",
|
||||||
|
"Categories.CreateCategoryDialog.CancelText": "Zrušiť",
|
||||||
|
"Categories.CreateCategoryDialog.CreateText": "Vytvoriť",
|
||||||
|
"Categories.CreateCategoryDialog.Placeholder": "Nazvite Vašu kategóriu",
|
||||||
|
"Categories.CreateCategoryDialog.UpdateText": "Zmeniť",
|
||||||
|
"CenterPanel.Login": "Prihlásiť sa",
|
||||||
|
"CenterPanel.Share": "Zdieľať",
|
||||||
|
"ChannelIntro.CreateBoard": "Vytvoriť nástenku",
|
||||||
|
"CloudMessage.cloud-server": "Získajte vlastný cloudový server zadarmo.",
|
||||||
|
"ColorOption.selectColor": "Vyberte {color} farbu",
|
||||||
"Comment.delete": "Odstrániť",
|
"Comment.delete": "Odstrániť",
|
||||||
"CommentsList.send": "Poslať",
|
"CommentsList.send": "Odoslať",
|
||||||
|
"ConfirmPerson.empty": "Prázdne",
|
||||||
|
"ConfirmPerson.search": "Vyhľadať...",
|
||||||
"ConfirmationDialog.cancel-action": "Zrušiť",
|
"ConfirmationDialog.cancel-action": "Zrušiť",
|
||||||
"ConfirmationDialog.confirm-action": "Potvrdiť",
|
"ConfirmationDialog.confirm-action": "Potvrdiť",
|
||||||
"ContentBlock.Delete": "Odstrániť",
|
"ContentBlock.Delete": "Odstrániť",
|
||||||
"ContentBlock.DeleteAction": "Odstrániť",
|
"ContentBlock.DeleteAction": "odstrániť",
|
||||||
"ContentBlock.addElement": "pridaj {type}",
|
"ContentBlock.addElement": "pridať {type}",
|
||||||
"ContentBlock.checkbox": "checkbox",
|
"ContentBlock.checkbox": "začiarkávacie pole",
|
||||||
"ContentBlock.divider": "oddeľovač",
|
"ContentBlock.divider": "oddeľovač",
|
||||||
"ContentBlock.editCardCheckbox": "označený-checkbox",
|
"ContentBlock.editCardCheckbox": "Začiarknuté pole",
|
||||||
"ContentBlock.editCardCheckboxText": "upraviť text karty",
|
"ContentBlock.editCardCheckboxText": "upraviť text karty",
|
||||||
"ContentBlock.editCardText": "upraviť text karty",
|
"ContentBlock.editCardText": "upraviť text karty",
|
||||||
"ContentBlock.editText": "Upraviť text...",
|
"ContentBlock.editText": "Upraviť text...",
|
||||||
"ContentBlock.image": "obrázok",
|
"ContentBlock.image": "obrázok",
|
||||||
"ContentBlock.insertAbove": "vlož nad",
|
"ContentBlock.insertAbove": "Vložiť nad",
|
||||||
"ContentBlock.moveDown": "Presuň dole",
|
"ContentBlock.moveBlock": "presunúť obsah karty",
|
||||||
"ContentBlock.moveUp": "Presuň hore",
|
"ContentBlock.moveDown": "Presunúť dole",
|
||||||
|
"ContentBlock.moveUp": "Presunúť hore",
|
||||||
"ContentBlock.text": "text",
|
"ContentBlock.text": "text",
|
||||||
|
"DateRange.clear": "Vyčistiť",
|
||||||
|
"DateRange.empty": "Prázdny",
|
||||||
|
"DateRange.endDate": "Koncový dátum",
|
||||||
|
"DateRange.today": "Dnes",
|
||||||
"DeleteBoardDialog.confirm-cancel": "Zrušiť",
|
"DeleteBoardDialog.confirm-cancel": "Zrušiť",
|
||||||
"DeleteBoardDialog.confirm-delete": "Odstrániť",
|
"DeleteBoardDialog.confirm-delete": "Odstrániť",
|
||||||
"DeleteBoardDialog.confirm-info": "Naozaj chcete odstrániť nástenku “{boardTitle}”? Odstránením vymažete všetky karty na tabuli.",
|
"DeleteBoardDialog.confirm-info": "Naozaj chcete odstrániť nástenku “{boardTitle}”? Odstránením vymažete všetky karty na tabuli.",
|
||||||
|
"DeleteBoardDialog.confirm-info-template": "Naozaj chcete odstrániť nástenkovú šablónu \"{boardTitle}\"?",
|
||||||
"DeleteBoardDialog.confirm-tite": "Potvrďte odstránenie nástenky",
|
"DeleteBoardDialog.confirm-tite": "Potvrďte odstránenie nástenky",
|
||||||
"Dialog.closeDialog": "Zavrieť dialog",
|
"DeleteBoardDialog.confirm-tite-template": "Potvrdiť odstránenie šablóny nástenky",
|
||||||
|
"Dialog.closeDialog": "Zatvoriť dialógové okno",
|
||||||
"EditableDayPicker.today": "Dnes",
|
"EditableDayPicker.today": "Dnes",
|
||||||
"Error.mobileweb": "Mobile web support is currently in early beta. Not all functionality may be present.",
|
"Error.mobileweb": "Podpora pre mobilné prehliadače je v skorej bete. Niektoré funkcionality môžu chýbať.",
|
||||||
"Error.websocket-closed": "Websocket pripojenie zlyhalo - prerušené. Skontrolujte konfiguráciu servera ak problém pretrváva.",
|
"Error.websocket-closed": "Websocket pripojenie zlyhalo - bolo prerušené. Pokiaľ problém pretrváva, skontrolujte konfiguráciu servera.",
|
||||||
|
"Filter.contains": "obsahuje",
|
||||||
|
"Filter.ends-with": "končí s",
|
||||||
"Filter.includes": "zahŕňa",
|
"Filter.includes": "zahŕňa",
|
||||||
|
"Filter.is": "je",
|
||||||
"Filter.is-empty": "je prázdny",
|
"Filter.is-empty": "je prázdny",
|
||||||
"Filter.is-not-empty": "nie je prázdny",
|
"Filter.is-not-empty": "nie je prázdny",
|
||||||
|
"Filter.is-not-set": "nie je nastavený",
|
||||||
|
"Filter.is-set": "je nastavený",
|
||||||
|
"Filter.not-contains": "neobsahuje",
|
||||||
|
"Filter.not-ends-with": "nekončí s",
|
||||||
"Filter.not-includes": "nezahŕňa",
|
"Filter.not-includes": "nezahŕňa",
|
||||||
|
"Filter.not-starts-with": "nezačína s",
|
||||||
|
"Filter.starts-with": "začína s",
|
||||||
|
"FilterByText.placeholder": "text filtra",
|
||||||
"FilterComponent.add-filter": "+ Pridaj filter",
|
"FilterComponent.add-filter": "+ Pridaj filter",
|
||||||
"FilterComponent.delete": "Odstrániť",
|
"FilterComponent.delete": "Odstrániť",
|
||||||
|
"FilterValue.empty": "(prázdny)",
|
||||||
|
"FindBoardsDialog.IntroText": "Vyhľadať nástenky",
|
||||||
|
"FindBoardsDialog.NoResultsFor": "Žiadne výsledky pre \"{searchQuery}\"",
|
||||||
|
"FindBoardsDialog.NoResultsSubtext": "Skontrolujte pravopis alebo vyskúšajte iný pojem.",
|
||||||
|
"FindBoardsDialog.SubTitle": "Nájdite nástenku písaním. Použite <b>HORE/DOLE</b> na prehliadanie, <b>ENTER</b> na vybratie a <b>ESC</b> na zrušenie",
|
||||||
|
"FindBoardsDialog.Title": "Nájsť nástenky",
|
||||||
|
"GroupBy.hideEmptyGroups": "Skryť {count} prázdnych skupín",
|
||||||
|
"GroupBy.showHiddenGroups": "Zobraziť {count} prázdnych skupín",
|
||||||
"GroupBy.ungroup": "Zrušiť zoskupenie",
|
"GroupBy.ungroup": "Zrušiť zoskupenie",
|
||||||
"KanbanCard.untitled": "Nepomenované",
|
"HideBoard.MenuOption": "Skryť nástenku",
|
||||||
"Mutator.new-card-from-template": "nová karta z template-u",
|
"KanbanCard.untitled": "Bez názvu",
|
||||||
"Mutator.new-template-from-card": "nový template z karty",
|
"MentionSuggestion.is-not-board-member": "(nie je členom nástenky)",
|
||||||
|
"Mutator.new-board-from-template": "nová nástenka zo šablóny",
|
||||||
|
"Mutator.new-card-from-template": "nová karta zo šablóny",
|
||||||
|
"Mutator.new-template-from-card": "nová šablóna z karty",
|
||||||
"PropertyMenu.Delete": "Odstrániť",
|
"PropertyMenu.Delete": "Odstrániť",
|
||||||
"PropertyMenu.changeType": "Zmeniť vlastnosť",
|
"PropertyMenu.changeType": "Zmeniť vlastnosť",
|
||||||
"PropertyMenu.selectType": "Vybrať vlastnosť",
|
"PropertyMenu.selectType": "Vybrať vlastnosť",
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"AppBar.Tooltip": "Chuyển sang các bảng đã liên kết",
|
||||||
|
"Attachment.Attachment-title": "Đính kèm",
|
||||||
|
"AttachmentBlock.DeleteAction": "xóa",
|
||||||
"BoardComponent.add-a-group": "+ Thêm nhóm",
|
"BoardComponent.add-a-group": "+ Thêm nhóm",
|
||||||
"BoardComponent.delete": "Xóa",
|
"BoardComponent.delete": "Xóa",
|
||||||
"BoardComponent.hidden-columns": "Cột ẩn",
|
"BoardComponent.hidden-columns": "Cột ẩn",
|
||||||
|
4
webapp/package-lock.json
generated
4
webapp/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "focalboard",
|
"name": "focalboard",
|
||||||
"version": "7.9.0",
|
"version": "7.10.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "focalboard",
|
"name": "focalboard",
|
||||||
"version": "7.9.0",
|
"version": "7.10.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@draft-js-plugins/editor": "^4.1.2",
|
"@draft-js-plugins/editor": "^4.1.2",
|
||||||
"@draft-js-plugins/emoji": "^4.6.0",
|
"@draft-js-plugins/emoji": "^4.6.0",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "focalboard",
|
"name": "focalboard",
|
||||||
"version": "7.9.0",
|
"version": "7.10.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "",
|
"description": "",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -10,7 +10,7 @@ exports[`components/sidebar/GlobalHeader header menu should match snapshot 1`] =
|
|||||||
/>
|
/>
|
||||||
<a
|
<a
|
||||||
class="GlobalHeaderComponent__button help-button"
|
class="GlobalHeaderComponent__button help-button"
|
||||||
href="https://www.focalboard.com/fwlink/doc-boards.html?v=7.9.0"
|
href="https://www.focalboard.com/fwlink/doc-boards.html?v=7.10.0"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
|
@ -51,9 +51,9 @@ exports[`components/sidebarSidebar dont show hidden boards 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="version"
|
class="version"
|
||||||
title="v7.9.0"
|
title="v7.10.0"
|
||||||
>
|
>
|
||||||
v7.9.0
|
v7.10.0
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -252,9 +252,9 @@ exports[`components/sidebarSidebar should assign default category if current boa
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="version"
|
class="version"
|
||||||
title="v7.9.0"
|
title="v7.10.0"
|
||||||
>
|
>
|
||||||
v7.9.0
|
v7.10.0
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -508,9 +508,9 @@ exports[`components/sidebarSidebar shouldnt do any category assignment is board
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="version"
|
class="version"
|
||||||
title="v7.9.0"
|
title="v7.10.0"
|
||||||
>
|
>
|
||||||
v7.9.0
|
v7.10.0
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -919,9 +919,9 @@ exports[`components/sidebarSidebar sidebar hidden 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="version"
|
class="version"
|
||||||
title="v7.9.0"
|
title="v7.10.0"
|
||||||
>
|
>
|
||||||
v7.9.0
|
v7.10.0
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1213,9 +1213,9 @@ exports[`components/sidebarSidebar some categories hidden 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="version"
|
class="version"
|
||||||
title="v7.9.0"
|
title="v7.10.0"
|
||||||
>
|
>
|
||||||
v7.9.0
|
v7.10.0
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,10 @@
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
color: rgba(var(--center-channel-color-rgb));
|
||||||
|
}
|
||||||
|
|
||||||
.octo-sidebar-item {
|
.octo-sidebar-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -203,6 +203,7 @@
|
|||||||
width: inherit;
|
width: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.MultiPerson.octo-propertyvalue,
|
||||||
.Person.octo-propertyvalue,
|
.Person.octo-propertyvalue,
|
||||||
.DateRange.octo-propertyvalue {
|
.DateRange.octo-propertyvalue {
|
||||||
overflow: unset;
|
overflow: unset;
|
||||||
|
@ -37,8 +37,8 @@ class Constants {
|
|||||||
static readonly titleColumnId = '__title'
|
static readonly titleColumnId = '__title'
|
||||||
static readonly badgesColumnId = '__badges'
|
static readonly badgesColumnId = '__badges'
|
||||||
|
|
||||||
static readonly versionString = '7.9.0'
|
static readonly versionString = '7.10.0'
|
||||||
static readonly versionDisplayString = 'Mar 2023'
|
static readonly versionDisplayString = 'Apr 2023'
|
||||||
|
|
||||||
static readonly archiveHelpPage = 'https://docs.mattermost.com/boards/migrate-to-boards.html'
|
static readonly archiveHelpPage = 'https://docs.mattermost.com/boards/migrate-to-boards.html'
|
||||||
static readonly imports = [
|
static readonly imports = [
|
||||||
|
@ -18,32 +18,40 @@ exports[`pages/welcome Welcome Page shows Explore Page 1`] = `
|
|||||||
>
|
>
|
||||||
Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.
|
Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.
|
||||||
</div>
|
</div>
|
||||||
<img
|
<div
|
||||||
alt="Boards Welcome Image"
|
class="WelcomePage__content"
|
||||||
class="WelcomePage__image WelcomePage__image--large"
|
|
||||||
src="test-file-stub"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
alt="Boards Welcome Image"
|
|
||||||
class="WelcomePage__image WelcomePage__image--small"
|
|
||||||
src="test-file-stub"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="Button filled size--large"
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<span>
|
<img
|
||||||
Take a tour
|
alt="Boards Welcome Image"
|
||||||
</span>
|
class="WelcomePage__image WelcomePage__image--large"
|
||||||
<i
|
src="test-file-stub"
|
||||||
class="CompassIcon icon-chevron-right Icon Icon--right"
|
|
||||||
/>
|
/>
|
||||||
</button>
|
<img
|
||||||
<a
|
alt="Boards Welcome Image"
|
||||||
class="skip"
|
class="WelcomePage__image WelcomePage__image--small"
|
||||||
>
|
src="test-file-stub"
|
||||||
No thanks, I'll figure it out myself
|
/>
|
||||||
</a>
|
<div
|
||||||
|
class="WelcomePage__buttons"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button filled size--large"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Take a tour
|
||||||
|
</span>
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-chevron-right Icon Icon--right"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
class="skip"
|
||||||
|
>
|
||||||
|
No thanks, I'll figure it out myself
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -67,32 +75,40 @@ exports[`pages/welcome Welcome Page shows Explore Page with subpath 1`] = `
|
|||||||
>
|
>
|
||||||
Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.
|
Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.
|
||||||
</div>
|
</div>
|
||||||
<img
|
<div
|
||||||
alt="Boards Welcome Image"
|
class="WelcomePage__content"
|
||||||
class="WelcomePage__image WelcomePage__image--large"
|
|
||||||
src="test-file-stub"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
alt="Boards Welcome Image"
|
|
||||||
class="WelcomePage__image WelcomePage__image--small"
|
|
||||||
src="test-file-stub"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="Button filled size--large"
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<span>
|
<img
|
||||||
Take a tour
|
alt="Boards Welcome Image"
|
||||||
</span>
|
class="WelcomePage__image WelcomePage__image--large"
|
||||||
<i
|
src="test-file-stub"
|
||||||
class="CompassIcon icon-chevron-right Icon Icon--right"
|
|
||||||
/>
|
/>
|
||||||
</button>
|
<img
|
||||||
<a
|
alt="Boards Welcome Image"
|
||||||
class="skip"
|
class="WelcomePage__image WelcomePage__image--small"
|
||||||
>
|
src="test-file-stub"
|
||||||
No thanks, I'll figure it out myself
|
/>
|
||||||
</a>
|
<div
|
||||||
|
class="WelcomePage__buttons"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button filled size--large"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Take a tour
|
||||||
|
</span>
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-chevron-right Icon Icon--right"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
class="skip"
|
||||||
|
>
|
||||||
|
No thanks, I'll figure it out myself
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,26 @@
|
|||||||
@media (max-height: 768px) {
|
@media (max-height: 768px) {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WelcomePage__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
@media (max-height: 800px) {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.WelcomePage__buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
@ -34,7 +54,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.skip {
|
.skip {
|
||||||
margin-top: 12px;
|
|
||||||
color: rgba(var(--link-color-rgb), 1);
|
color: rgba(var(--link-color-rgb), 1);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
@ -127,59 +127,63 @@ const WelcomePage = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* This image will be rendered on large screens over 2000px */}
|
<div className='WelcomePage__content'>
|
||||||
<img
|
{/* This image will be rendered on large screens over 2000px */}
|
||||||
src={Utils.buildURL(BoardWelcomePNG, true)}
|
<img
|
||||||
className='WelcomePage__image WelcomePage__image--large'
|
src={Utils.buildURL(BoardWelcomePNG, true)}
|
||||||
alt='Boards Welcome Image'
|
className='WelcomePage__image WelcomePage__image--large'
|
||||||
/>
|
alt='Boards Welcome Image'
|
||||||
|
/>
|
||||||
|
|
||||||
{/* This image will be rendered on small screens below 2000px */}
|
{/* This image will be rendered on small screens below 2000px */}
|
||||||
<img
|
<img
|
||||||
src={Utils.buildURL(BoardWelcomeSmallPNG, true)}
|
src={Utils.buildURL(BoardWelcomeSmallPNG, true)}
|
||||||
className='WelcomePage__image WelcomePage__image--small'
|
className='WelcomePage__image WelcomePage__image--small'
|
||||||
alt='Boards Welcome Image'
|
alt='Boards Welcome Image'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{me?.is_guest !== true &&
|
<div className='WelcomePage__buttons'>
|
||||||
<Button
|
{me?.is_guest !== true &&
|
||||||
onClick={startTour}
|
<Button
|
||||||
filled={true}
|
onClick={startTour}
|
||||||
size='large'
|
filled={true}
|
||||||
icon={
|
size='large'
|
||||||
<CompassIcon
|
icon={
|
||||||
icon='chevron-right'
|
<CompassIcon
|
||||||
className='Icon Icon--right'
|
icon='chevron-right'
|
||||||
/>}
|
className='Icon Icon--right'
|
||||||
rightIcon={true}
|
/>}
|
||||||
>
|
rightIcon={true}
|
||||||
<FormattedMessage
|
>
|
||||||
id='WelcomePage.Explore.Button'
|
<FormattedMessage
|
||||||
defaultMessage='Take a tour'
|
id='WelcomePage.Explore.Button'
|
||||||
/>
|
defaultMessage='Take a tour'
|
||||||
</Button>}
|
/>
|
||||||
|
</Button>}
|
||||||
|
|
||||||
{me?.is_guest !== true &&
|
{me?.is_guest !== true &&
|
||||||
<a
|
<a
|
||||||
className='skip'
|
className='skip'
|
||||||
onClick={skipTour}
|
onClick={skipTour}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='WelcomePage.NoThanks.Text'
|
id='WelcomePage.NoThanks.Text'
|
||||||
defaultMessage="No thanks, I'll figure it out myself"
|
defaultMessage="No thanks, I'll figure it out myself"
|
||||||
/>
|
/>
|
||||||
</a>}
|
</a>}
|
||||||
{me?.is_guest === true &&
|
{me?.is_guest === true &&
|
||||||
<Button
|
<Button
|
||||||
onClick={skipTour}
|
onClick={skipTour}
|
||||||
filled={true}
|
filled={true}
|
||||||
size='large'
|
size='large'
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='WelcomePage.StartUsingIt.Text'
|
id='WelcomePage.StartUsingIt.Text'
|
||||||
defaultMessage='Start using it'
|
defaultMessage='Start using it'
|
||||||
/>
|
/>
|
||||||
</Button>}
|
</Button>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -34,6 +34,23 @@ exports[`properties/dateRange handle clear 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`properties/dateRange returns component with new date after prop change 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="DateRange octo-propertyvalue"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
June 15
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`properties/dateRange returns default correctly 1`] = `
|
exports[`properties/dateRange returns default correctly 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -315,4 +315,36 @@ describe('properties/dateRange', () => {
|
|||||||
|
|
||||||
expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify({from: today}))
|
expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify({from: today}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('returns component with new date after prop change', () => {
|
||||||
|
const component = wrapIntl(
|
||||||
|
<DateProp
|
||||||
|
property={new DateProperty()}
|
||||||
|
propertyValue=''
|
||||||
|
showEmptyPlaceholder={false}
|
||||||
|
readOnly={false}
|
||||||
|
board={{...board}}
|
||||||
|
card={{...card}}
|
||||||
|
propertyTemplate={propertyTemplate}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const {container, rerender} = render(component)
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
wrapIntl(
|
||||||
|
<DateProp
|
||||||
|
property={new DateProperty()}
|
||||||
|
propertyValue={'{"from": ' + June15.getTime().toString() + '}'}
|
||||||
|
showEmptyPlaceholder={false}
|
||||||
|
readOnly={false}
|
||||||
|
board={{...board}}
|
||||||
|
card={{...card}}
|
||||||
|
propertyTemplate={propertyTemplate}
|
||||||
|
/>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React, {useMemo, useState, useCallback} from 'react'
|
import React, {useMemo, useState, useCallback, useEffect} from 'react'
|
||||||
import {useIntl} from 'react-intl'
|
import {useIntl} from 'react-intl'
|
||||||
import {DateUtils} from 'react-day-picker'
|
import {DateUtils} from 'react-day-picker'
|
||||||
import MomentLocaleUtils from 'react-day-picker/moment'
|
import MomentLocaleUtils from 'react-day-picker/moment'
|
||||||
@ -58,6 +58,12 @@ function DateRange(props: PropertyProps): JSX.Element {
|
|||||||
const [value, setValue] = useState(propertyValue)
|
const [value, setValue] = useState(propertyValue)
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value !== propertyValue) {
|
||||||
|
setValue(propertyValue)
|
||||||
|
}
|
||||||
|
}, [propertyValue, setValue])
|
||||||
|
|
||||||
const onChange = useCallback((newValue) => {
|
const onChange = useCallback((newValue) => {
|
||||||
if (value !== newValue) {
|
if (value !== newValue) {
|
||||||
setValue(newValue)
|
setValue(newValue)
|
||||||
|
@ -35,11 +35,14 @@ const URLProperty = (props: PropertyProps): JSX.Element => {
|
|||||||
if (value !== (props.card.fields.properties[props.propertyTemplate?.id || ''] || '')) {
|
if (value !== (props.card.fields.properties[props.propertyTemplate?.id || ''] || '')) {
|
||||||
mutator.changePropertyValue(props.board.id, props.card, props.propertyTemplate?.id || '', value)
|
mutator.changePropertyValue(props.board.id, props.card, props.propertyTemplate?.id || '', value)
|
||||||
}
|
}
|
||||||
}, [props.card, props.propertyTemplate, value])
|
}, [props.board.id, props.card, props.propertyTemplate?.id, value])
|
||||||
|
|
||||||
const saveTextPropertyRef = useRef<() => void>(saveTextProperty)
|
const saveTextPropertyRef = useRef<() => void>(saveTextProperty)
|
||||||
saveTextPropertyRef.current = saveTextProperty
|
if (props.readOnly) {
|
||||||
|
saveTextPropertyRef.current = () => null
|
||||||
|
} else {
|
||||||
|
saveTextPropertyRef.current = saveTextProperty
|
||||||
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
saveTextPropertyRef.current && saveTextPropertyRef.current()
|
saveTextPropertyRef.current && saveTextPropertyRef.current()
|
||||||
|
@ -50,6 +50,7 @@ export const TelemetryActions = {
|
|||||||
LimitCardLimitReached: 'limit_cardLimitReached',
|
LimitCardLimitReached: 'limit_cardLimitReached',
|
||||||
LimitCardLimitLinkOpen: 'limit_cardLimitLinkOpen',
|
LimitCardLimitLinkOpen: 'limit_cardLimitLinkOpen',
|
||||||
VersionMoreInfo: 'version_more_info',
|
VersionMoreInfo: 'version_more_info',
|
||||||
|
ClickChannelsRHSBoard: 'click_board_in_channels_RHS',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IEventProps {
|
interface IEventProps {
|
||||||
|
Loading…
Reference in New Issue
Block a user