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:
|
||||
|
||||
ci-ubuntu-server:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@ -44,7 +44,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
@ -54,7 +54,7 @@ jobs:
|
||||
run: cd focalboard; make server-test-${{matrix['db']}}
|
||||
|
||||
ci-ubuntu-webapp:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
@ -74,7 +74,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
- name: npm ci
|
||||
run: |
|
||||
cd focalboard/webapp && npm ci && cd -
|
||||
@ -132,7 +132,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
@ -169,7 +169,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
|
13
.github/workflows/dev-release.yml
vendored
13
.github/workflows/dev-release.yml
vendored
@ -14,8 +14,7 @@ env:
|
||||
jobs:
|
||||
|
||||
ubuntu:
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@ -34,7 +33,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
|
||||
- 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
|
||||
@ -110,7 +109,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
- 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
|
||||
|
||||
@ -168,7 +167,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
- 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
|
||||
|
||||
@ -218,7 +217,7 @@ jobs:
|
||||
path: ${{ github.workspace }}/focalboard/win-wpf/dist/focalboard-win.zip
|
||||
|
||||
plugin:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -238,7 +237,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
|
||||
- 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
|
||||
|
6
.github/workflows/lint-server.yml
vendored
6
.github/workflows/lint-server.yml
vendored
@ -13,7 +13,7 @@ env:
|
||||
|
||||
jobs:
|
||||
down-migrations:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
golangci:
|
||||
name: plugin
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
@ -48,7 +48,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
- 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
|
||||
- name: lint
|
||||
|
12
.github/workflows/prod-release.yml
vendored
12
.github/workflows/prod-release.yml
vendored
@ -9,7 +9,7 @@ env:
|
||||
jobs:
|
||||
|
||||
ubuntu:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -30,7 +30,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
|
||||
- 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
|
||||
@ -106,7 +106,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
|
||||
- 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
|
||||
@ -165,7 +165,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
|
||||
- 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
|
||||
@ -216,7 +216,7 @@ jobs:
|
||||
path: ${{ github.workspace }}/focalboard/win-wpf/dist/focalboard-win.zip
|
||||
|
||||
plugin-release:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -237,7 +237,7 @@ jobs:
|
||||
repository: "mattermost/mattermost-server"
|
||||
fetch-depth: "20"
|
||||
path: "mattermost-server"
|
||||
ref : "master"
|
||||
ref : "b61c096497ac1f22f64b77afe58d0dd5a72b38f1"
|
||||
|
||||
- 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
|
||||
|
@ -1,10 +1,10 @@
|
||||
# This dockerfile is used to build Focalboard for Linux
|
||||
# it builds all the parts inside the container and the last stage just holds the
|
||||
# package that can be extracted using docker cp command
|
||||
# ie
|
||||
# docker build -f Dockerfile.build --no-cache -t focalboard-build:dirty .
|
||||
# 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 .
|
||||
# This Dockerfile is used to build Focalboard for Linux. It builds all the parts inside the image
|
||||
# and the last stage just holds the package which is then copied back to the host.
|
||||
#
|
||||
# docker buildx build -f Dockerfile.build --no-cache --platform linux/amd64 -t focalboard-build:dirty --output out .
|
||||
# docker buildx build -f Dockerfile.build --no-cache --platform linux/arm64 -t focalboard-build:dirty --output out .
|
||||
#
|
||||
# Afterwards the packages can be found in the ./out folder.
|
||||
|
||||
# build frontend
|
||||
FROM node:16.3.0@sha256:ca6daf1543242acb0ca59ff425509eab7defb9452f6ae07c156893db06c7a9a4 AS frontend
|
||||
@ -12,8 +12,10 @@ FROM node:16.3.0@sha256:ca6daf1543242acb0ca59ff425509eab7defb9452f6ae07c156893db
|
||||
WORKDIR /webapp
|
||||
COPY webapp .
|
||||
|
||||
RUN npm install --no-optional
|
||||
RUN npm run pack
|
||||
### 'CPPFLAGS="-DPNG_ARM_NEON_OPT=0"' Needed To Avoid Bug Described in: https://github.com/imagemin/optipng-bin/issues/118#issuecomment-1019838562
|
||||
### 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
|
||||
FROM golang:1.18.3@sha256:b203dc573d81da7b3176264bfa447bd7c10c9347689be40540381838d75eebef AS backend
|
||||
@ -21,13 +23,13 @@ FROM golang:1.18.3@sha256:b203dc573d81da7b3176264bfa447bd7c10c9347689be405403818
|
||||
COPY . .
|
||||
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 make server-linux
|
||||
RUN make server-linux-package-docker
|
||||
RUN EXCLUDE_PLUGIN=true EXCLUDE_SERVER=true EXCLUDE_ENTERPRISE=true make server-linux arch=${TARGETARCH}
|
||||
RUN make server-linux-package-docker arch=${TARGETARCH}
|
||||
|
||||
# just hold the packages to output later
|
||||
FROM alpine:3.12@sha256:c75ac27b49326926b803b9ed43bf088bc220d22556de1bc5f72d742c91398f69 AS dist
|
||||
|
||||
WORKDIR /dist
|
||||
|
||||
COPY --from=backend /go/dist/focalboard-server-linux-amd64.tar.gz .
|
||||
# Copy package back to host
|
||||
FROM scratch AS dist
|
||||
ARG TARGETARCH
|
||||
COPY --from=backend /go/dist/focalboard-server-linux-${TARGETARCH}.tar.gz .
|
||||
|
4
Makefile
4
Makefile
@ -63,7 +63,7 @@ endif
|
||||
server-linux: setup-go-work ## Build server for Linux.
|
||||
mkdir -p bin/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.
|
||||
mkdir -p bin/docker
|
||||
@ -101,7 +101,7 @@ server-linux-package-docker:
|
||||
cp NOTICE.txt package/${PACKAGE_FOLDER}
|
||||
cp webapp/NOTICE.txt package/${PACKAGE_FOLDER}/webapp-NOTICE.txt
|
||||
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
|
||||
|
||||
generate: ## Install and run code generators.
|
||||
|
@ -6,7 +6,7 @@
|
||||
"support_url": "https://github.com/mattermost/focalboard/issues",
|
||||
"release_notes_url": "https://github.com/mattermost/focalboard/releases",
|
||||
"icon_path": "assets/starter-template-icon.svg",
|
||||
"version": "7.9.0",
|
||||
"version": "7.10.0",
|
||||
"min_server_version": "7.2.0",
|
||||
"server": {
|
||||
"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",
|
||||
"release_notes_url": "https://github.com/mattermost/focalboard/releases",
|
||||
"icon_path": "assets/starter-template-icon.svg",
|
||||
"version": "7.9.0",
|
||||
"version": "7.10.0",
|
||||
"min_server_version": "7.2.0",
|
||||
"server": {
|
||||
"executables": {
|
||||
@ -45,8 +45,7 @@ const manifestStr = `
|
||||
"type": "bool",
|
||||
"help_text": "This allows board editors to share boards that can be accessed by anyone with the link.",
|
||||
"placeholder": "",
|
||||
"default": false,
|
||||
"hosting": ""
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ const CreateBoardFromTemplate = (props: Props) => {
|
||||
|
||||
let boardsAndBlocks = undefined
|
||||
|
||||
if (selectedBoardTemplateId === EMPTY_BOARD) {
|
||||
if (templateIdRef.current === EMPTY_BOARD) {
|
||||
boardsAndBlocks = await mutator.addEmptyBoard(teamId, intl)
|
||||
} else {
|
||||
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 './rhsChannelBoardItem.scss'
|
||||
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)
|
||||
|
||||
@ -36,6 +38,10 @@ const RHSChannelBoardItem = (props: Props) => {
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ func jsonStringResponse(w http.ResponseWriter, code int, message string) { //nol
|
||||
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")
|
||||
w.WriteHeader(code)
|
||||
_, _ = w.Write(json)
|
||||
|
@ -8,6 +8,8 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -20,9 +22,30 @@ import (
|
||||
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||
|
||||
"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
|
||||
// swagger:model
|
||||
type FileUploadResponse struct {
|
||||
@ -123,37 +146,12 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
|
||||
auditRec.AddMeta("teamID", board.TeamID)
|
||||
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) {
|
||||
a.errorResponse(w, r, err)
|
||||
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 != "" {
|
||||
// prior to moving from workspaces to teams, the filepath was constructed from
|
||||
// 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()
|
||||
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()
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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
|
||||
|
||||
if patch.Type != nil || patch.ChannelID != nil {
|
||||
testChannel := ""
|
||||
if patch.ChannelID != nil && *patch.ChannelID == "" {
|
||||
var err error
|
||||
oldMembers, err = a.GetMembersForBoard(boardID)
|
||||
if err != nil {
|
||||
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)
|
||||
@ -372,7 +375,17 @@ func (a *App) PatchBoard(patch *model.BoardPatch, boardID, userID string) (*mode
|
||||
}
|
||||
oldChannelID = board.ChannelID
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -185,6 +185,7 @@ func TestPatchBoard(t *testing.T) {
|
||||
|
||||
// Type not null will retrieve team members
|
||||
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(
|
||||
&model.Board{
|
||||
@ -399,6 +400,104 @@ func TestPatchBoard(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
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) {
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||
|
||||
"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)
|
||||
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)
|
||||
if appErr != nil {
|
||||
@ -45,7 +46,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
|
||||
CreateAt: now,
|
||||
UpdateAt: now,
|
||||
DeleteAt: 0,
|
||||
Path: emptyString,
|
||||
Path: filePath,
|
||||
ThumbnailPath: emptyString,
|
||||
PreviewPath: emptyString,
|
||||
Name: filename,
|
||||
@ -59,6 +60,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
|
||||
Content: "",
|
||||
RemoteId: nil,
|
||||
}
|
||||
|
||||
err := a.store.SaveFileInfo(fileInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -77,6 +79,7 @@ func (a *App) GetFileInfo(filename string) (*mmModel.FileInfo, error) {
|
||||
// will be the fileinfo id.
|
||||
parts := strings.Split(filename, ".")
|
||||
fileInfoID := parts[0][1:]
|
||||
|
||||
fileInfo, err := a.store.GetFileInfo(fileInfoID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -85,6 +88,40 @@ func (a *App) GetFileInfo(filename string) (*mmModel.FileInfo, error) {
|
||||
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) {
|
||||
filePath := filepath.Join(teamID, rootID, filename)
|
||||
exists, err := a.filesBackend.FileExists(filePath)
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -195,8 +196,8 @@ func TestSaveFile(t *testing.T) {
|
||||
|
||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||
paths := strings.Split(path, string(os.PathSeparator))
|
||||
assert.Equal(t, "1", paths[0])
|
||||
assert.Equal(t, testBoardID, paths[1])
|
||||
assert.Equal(t, "boards", paths[0])
|
||||
assert.Equal(t, time.Now().Format("20060102"), paths[1])
|
||||
fileName = paths[2]
|
||||
return int64(10)
|
||||
}
|
||||
@ -219,8 +220,8 @@ func TestSaveFile(t *testing.T) {
|
||||
|
||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||
paths := strings.Split(path, string(os.PathSeparator))
|
||||
assert.Equal(t, "1", paths[0])
|
||||
assert.Equal(t, "test-board-id", paths[1])
|
||||
assert.Equal(t, "boards", paths[0])
|
||||
assert.Equal(t, time.Now().Format("20060102"), paths[1])
|
||||
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
||||
return int64(10)
|
||||
}
|
||||
@ -243,8 +244,8 @@ func TestSaveFile(t *testing.T) {
|
||||
|
||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||
paths := strings.Split(path, string(os.PathSeparator))
|
||||
assert.Equal(t, "1", paths[0])
|
||||
assert.Equal(t, "test-board-id", paths[1])
|
||||
assert.Equal(t, "boards", paths[0])
|
||||
assert.Equal(t, time.Now().Format("20060102"), paths[1])
|
||||
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
||||
return int64(10)
|
||||
}
|
||||
@ -304,3 +305,80 @@ func TestGetFileInfo(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
defer closeBody(r)
|
||||
|
||||
var cards []*model.Card
|
||||
if err := json.NewDecoder(r.Body).Decode(&cards); err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
@ -398,6 +400,8 @@ func (c *Client) PatchCard(cardID string, cardPatch *model.CardPatch, disableNot
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
|
||||
defer closeBody(r)
|
||||
|
||||
var cardNew *model.Card
|
||||
if err := json.NewDecoder(r.Body).Decode(&cardNew); err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
@ -412,6 +416,8 @@ func (c *Client) GetCard(cardID string) (*model.Card, *Response) {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
|
||||
defer closeBody(r)
|
||||
|
||||
var card *model.Card
|
||||
if err := json.NewDecoder(r.Body).Decode(&card); err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
@ -450,6 +456,7 @@ func (c *Client) DeleteCategory(teamID, categoryID string) *Response {
|
||||
return BuildErrorResponse(r, err)
|
||||
}
|
||||
|
||||
defer closeBody(r)
|
||||
return BuildResponse(r)
|
||||
}
|
||||
|
||||
@ -1049,6 +1056,7 @@ func (c *Client) HideBoard(teamID, categoryID, boardID string) *Response {
|
||||
return BuildErrorResponse(r, err)
|
||||
}
|
||||
|
||||
defer closeBody(r)
|
||||
return BuildResponse(r)
|
||||
}
|
||||
|
||||
@ -1058,5 +1066,6 @@ func (c *Client) UnhideBoard(teamID, categoryID, boardID string) *Response {
|
||||
return BuildErrorResponse(r, err)
|
||||
}
|
||||
|
||||
defer closeBody(r)
|
||||
return BuildResponse(r)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
// It should be maintained in chronological order with most current
|
||||
// release at the front of the list.
|
||||
var versions = []string{
|
||||
"7.10.0",
|
||||
"7.9.0",
|
||||
"7.8.0",
|
||||
"7.7.0",
|
||||
|
@ -26,6 +26,7 @@ const (
|
||||
CategoryUUIDIDMigrationKey = "CategoryUuidIdMigrationComplete"
|
||||
TeamLessBoardsMigrationKey = "TeamLessBoardsMigrationComplete"
|
||||
DeletedMembershipBoardsMigrationKey = "DeletedMembershipBoardsMigrationComplete"
|
||||
DeDuplicateCategoryBoardTableMigrationKey = "DeDuplicateCategoryBoardTableComplete"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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",
|
||||
"size",
|
||||
"delete_at",
|
||||
"path",
|
||||
"archived",
|
||||
).
|
||||
Values(
|
||||
@ -31,6 +32,7 @@ func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) er
|
||||
fileInfo.Extension,
|
||||
fileInfo.Size,
|
||||
fileInfo.DeleteAt,
|
||||
fileInfo.Path,
|
||||
false,
|
||||
)
|
||||
|
||||
@ -57,6 +59,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo,
|
||||
"extension",
|
||||
"size",
|
||||
"archived",
|
||||
"path",
|
||||
).
|
||||
From(s.tablePrefix + "file_info").
|
||||
Where(sq.Eq{"Id": id})
|
||||
@ -73,6 +76,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo,
|
||||
&fileInfo.Extension,
|
||||
&fileInfo.Size,
|
||||
&fileInfo.Archived,
|
||||
&fileInfo.Path,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
@ -36,6 +36,7 @@ const (
|
||||
uniqueIDsMigrationRequiredVersion = 14
|
||||
teamLessBoardsMigrationRequiredVersion = 18
|
||||
categoriesUUIDIDMigrationRequiredVersion = 20
|
||||
deDuplicateCategoryBoards = 35
|
||||
|
||||
tempSchemaMigrationTableName = "temp_schema_migration"
|
||||
)
|
||||
@ -248,6 +249,15 @@ func (s *SQLStore) runMigrationSequence(engine *morph.Morph, driver drivers.Driv
|
||||
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 ====================",
|
||||
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) {
|
||||
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
||||
normTableName := normalizeTablename(s.schemaName, tableName)
|
||||
normTableName := s.normalizeTablename(tableName)
|
||||
|
||||
switch s.dbType {
|
||||
case model.SqliteDBType:
|
||||
@ -348,7 +358,7 @@ func (s *SQLStore) genAddColumnIfNeeded(tableName, columnName, datatype, constra
|
||||
|
||||
func (s *SQLStore) genDropColumnIfNeeded(tableName, columnName string) (string, error) {
|
||||
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
||||
normTableName := normalizeTablename(s.schemaName, tableName)
|
||||
normTableName := s.normalizeTablename(tableName)
|
||||
|
||||
switch s.dbType {
|
||||
case model.SqliteDBType:
|
||||
@ -385,7 +395,7 @@ func (s *SQLStore) genDropColumnIfNeeded(tableName, columnName string) (string,
|
||||
func (s *SQLStore) genCreateIndexIfNeeded(tableName, columns string) (string, error) {
|
||||
indexName := getIndexName(tableName, columns)
|
||||
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
||||
normTableName := normalizeTablename(s.schemaName, tableName)
|
||||
normTableName := s.normalizeTablename(tableName)
|
||||
|
||||
switch s.dbType {
|
||||
case model.SqliteDBType:
|
||||
@ -425,7 +435,7 @@ func (s *SQLStore) genRenameTableIfNeeded(oldTableName, newTableName string) (st
|
||||
oldTableName = addPrefixIfNeeded(oldTableName, s.tablePrefix)
|
||||
newTableName = addPrefixIfNeeded(newTableName, s.tablePrefix)
|
||||
|
||||
normOldTableName := normalizeTablename(s.schemaName, oldTableName)
|
||||
normOldTableName := s.normalizeTablename(oldTableName)
|
||||
|
||||
vars := map[string]string{
|
||||
"schema": s.schemaName,
|
||||
@ -472,7 +482,7 @@ func (s *SQLStore) genRenameTableIfNeeded(oldTableName, newTableName string) (st
|
||||
|
||||
func (s *SQLStore) genRenameColumnIfNeeded(tableName, oldColumnName, newColumnName, dataType string) (string, error) {
|
||||
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
||||
normTableName := normalizeTablename(s.schemaName, tableName)
|
||||
normTableName := s.normalizeTablename(tableName)
|
||||
|
||||
vars := map[string]string{
|
||||
"schema": s.schemaName,
|
||||
@ -610,7 +620,7 @@ func (s *SQLStore) doesColumnExist(tableName, columnName string) (bool, error) {
|
||||
|
||||
func (s *SQLStore) genAddConstraintIfNeeded(tableName, constraintName, constraintType, constraintDefinition string) (string, error) {
|
||||
tableName = addPrefixIfNeeded(tableName, s.tablePrefix)
|
||||
normTableName := normalizeTablename(s.schemaName, tableName)
|
||||
normTableName := s.normalizeTablename(tableName)
|
||||
|
||||
var query string
|
||||
|
||||
@ -676,8 +686,12 @@ func addPrefixIfNeeded(s, prefix string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func normalizeTablename(schemaName, tableName string) string {
|
||||
if schemaName != "" && !strings.HasPrefix(tableName, schemaName+".") {
|
||||
func (s *SQLStore) normalizeTablename(tableName string) string {
|
||||
if s.schemaName != "" && !strings.HasPrefix(tableName, s.schemaName+".") {
|
||||
schemaName := s.schemaName
|
||||
if s.dbType == model.MysqlDBType {
|
||||
schemaName = "`" + schemaName + "`"
|
||||
}
|
||||
tableName = schemaName + "." + tableName
|
||||
}
|
||||
return tableName
|
||||
|
@ -0,0 +1 @@
|
||||
SELECT 1;
|
@ -0,0 +1 @@
|
||||
{{ addColumnIfNeeded "file_info" "path" "varchar(512)" "" }}
|
@ -12,6 +12,7 @@ import (
|
||||
var dataMigrationSystemSettings = map[string]string{
|
||||
"UniqueIDsMigrationComplete": "true",
|
||||
"CategoryUuidIdMigrationComplete": "true",
|
||||
"DeDuplicateCategoryBoardTableComplete": "true",
|
||||
}
|
||||
|
||||
func addBaseSettings(m map[string]string) map[string]string {
|
||||
|
@ -2,6 +2,7 @@ package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
@ -120,3 +121,7 @@ func DedupeStringArr(arr []string) []string {
|
||||
|
||||
return dedupedArr
|
||||
}
|
||||
|
||||
func GetBaseFilePath() string {
|
||||
return path.Join("boards", time.Now().Format("20060102"))
|
||||
}
|
||||
|
@ -1,5 +1,15 @@
|
||||
{
|
||||
"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.delete": "حذف",
|
||||
"BoardComponent.hidden-columns": "الأعمدة المخفية",
|
||||
@ -155,9 +165,15 @@
|
||||
"FindBoardsDialog.NoResultsFor": "لا يوجد نتيجة للبحث \"{searchQuery}\"",
|
||||
"FindBoardsDialog.NoResultsSubtext": "اختر بحث آخر أو تأكد من الأخطاء الإملائية.",
|
||||
"FindBoardsDialog.Title": "البحث عن ألواح",
|
||||
"GroupBy.ungroup": "إلغاء التجميع",
|
||||
"KanbanCard.untitled": "بدون عنوان",
|
||||
"Mutator.new-card-from-template": "بطاقة جديدة من نموذج",
|
||||
"Mutator.new-template-from-card": "نموذج جديد من بطاقة",
|
||||
"OnboardingTour.AddComments.Title": "إضافة تعليقات",
|
||||
"OnboardingTour.AddDescription.Title": "اضافة وصف",
|
||||
"OnboardingTour.AddProperties.Title": "إضافة خواص",
|
||||
"OnboardingTour.AddView.Body": "انتقل هنا لإنشاء عرض جديد لتنظيم لوحتك باستخدام تخطيطات مختلفة.",
|
||||
"OnboardingTour.AddView.Title": "إضافة عرض جديد",
|
||||
"OnboardingTour.CopyLink.Title": "نسخ الرابط",
|
||||
"PropertyMenu.Delete": "حذف",
|
||||
"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.delete": "Eliminar",
|
||||
"BoardComponent.hidden-columns": "Columnes ocultes",
|
||||
"BoardComponent.hide": "Amagar",
|
||||
"BoardComponent.new": "+ Nou",
|
||||
"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",
|
||||
"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-icon": "Afegeix icona",
|
||||
"CardDetail.add-property": "+ Afegeix propietat",
|
||||
"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...",
|
||||
"CardDialog.editing-template": "Estas editant una plantilla.",
|
||||
"CardDialog.nocard": "Aquesta targeta no existeix o és innaccesible.",
|
||||
@ -47,18 +111,18 @@
|
||||
"PropertyMenu.changeType": "Canviar el tipus de propietat",
|
||||
"PropertyMenu.typeTitle": "Tipus",
|
||||
"PropertyType.Checkbox": "casella de verificació",
|
||||
"PropertyType.CreatedBy": "Creat Per",
|
||||
"PropertyType.CreatedBy": "Creada per",
|
||||
"PropertyType.CreatedTime": "Moment de creació",
|
||||
"PropertyType.Date": "Data",
|
||||
"PropertyType.Email": "Correu electrònic",
|
||||
"PropertyType.MultiSelect": "Multi selecció",
|
||||
"PropertyType.MultiSelect": "Selecció múltiple",
|
||||
"PropertyType.Number": "Número",
|
||||
"PropertyType.Person": "Persona",
|
||||
"PropertyType.Phone": "Telèfon",
|
||||
"PropertyType.Select": "Selecciona",
|
||||
"PropertyType.Text": "Text",
|
||||
"PropertyType.UpdatedBy": "Actualitzat per",
|
||||
"PropertyType.UpdatedTime": "Moment de actualització",
|
||||
"PropertyType.UpdatedBy": "Última actualització feta per",
|
||||
"PropertyType.UpdatedTime": "Moment d'actualització",
|
||||
"RegistrationLink.confirmRegenerateToken": "Això invalidarà enllaços compartits anteriorment. Continuar?",
|
||||
"RegistrationLink.copiedLink": "Copiat!",
|
||||
"RegistrationLink.copyLink": "Copiar enllaç",
|
||||
@ -113,7 +177,7 @@
|
||||
"ViewHeader.group-by": "Agrupar per: {property}",
|
||||
"ViewHeader.new": "Nou",
|
||||
"ViewHeader.properties": "Propietats",
|
||||
"ViewHeader.search-text": "Cercar text",
|
||||
"ViewHeader.search-text": "Cerca tarjetes",
|
||||
"ViewHeader.select-a-template": "Selecciona una plantilla",
|
||||
"ViewHeader.sort": "Ordenar",
|
||||
"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.delete": "Borrar",
|
||||
"BoardComponent.hidden-columns": "Columnas Ocultas",
|
||||
@ -10,21 +21,39 @@
|
||||
"BoardMember.schemeAdmin": "Administrador",
|
||||
"BoardMember.schemeEditor": "Editor",
|
||||
"BoardMember.schemeNone": "Ninguno",
|
||||
"BoardPage.newVersion": "Una nueva versión de Board está disponible, haz click aquí para recargar.",
|
||||
"BoardPage.syncFailed": "El tablero puede estar eliminado o el acceso fue revocado.",
|
||||
"BoardTemplateSelector.add-template": "Nueva plantilla",
|
||||
"BoardTemplateSelector.create-empty-board": "Crear pizarra vacía",
|
||||
"BoardTemplateSelector.delete-template": "Suprimir",
|
||||
"BoardMember.schemeViewer": "Visualizador",
|
||||
"BoardMember.unlinkChannel": "Desvincular",
|
||||
"BoardPage.newVersion": "Una nueva versión de Boards está disponible, haz clic aquí para recargar.",
|
||||
"BoardPage.syncFailed": "El tablero puede haber sido eliminado o el acceso revocado.",
|
||||
"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.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",
|
||||
"BoardsSwitcher.Title": "Encontrar tableros",
|
||||
"BoardsUnfurl.Limited": "Los detalles adicionales están ocultos debido a que la tarjeta ha sido archivada",
|
||||
"BoardsUnfurl.Updated": "Actualizado {time}",
|
||||
"Calculations.Options.count.displayName": "Cantidad",
|
||||
"Calculations.Options.count.label": "Cantidad",
|
||||
"Calculations.Options.average.displayName": "Promedio",
|
||||
"Calculations.Options.average.label": "Promedio",
|
||||
"Calculations.Options.count.displayName": "Contar",
|
||||
"Calculations.Options.count.label": "Contar",
|
||||
"Calculations.Options.countChecked.displayName": "Marcado",
|
||||
"Calculations.Options.countChecked.label": "Contar marcados",
|
||||
"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.dateRange.displayName": "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.label": "Máx",
|
||||
"Calculations.Options.median.displayName": "Mediana",
|
||||
@ -34,17 +63,54 @@
|
||||
"Calculations.Options.none.displayName": "Calcular",
|
||||
"Calculations.Options.none.label": "Ninguna",
|
||||
"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.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-icon": "Añadir icono",
|
||||
"CardDetail.add-property": "+ Añadir propiedad",
|
||||
"CardDetail.addCardText": "añade texto a la tarjeta",
|
||||
"CardDetail.moveContent": "mover contenido de la tarjeta",
|
||||
"CardDetail.addCardText": "agregar texto a 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...",
|
||||
"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.property-deleted": "¡{nombre de la propiedad} ha sido eliminado exitosamente!",
|
||||
"CardDetailProperty.confirm-delete-heading": "Confirmar eliminación de la propiedad",
|
||||
"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.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",
|
||||
"Comment.delete": "Borrar",
|
||||
"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.delete": "Slett",
|
||||
"BoardComponent.hidden-columns": "Skjulte kolonner",
|
||||
"BoardComponent.hide": "Skjul",
|
||||
"BoardComponent.new": "+ Ny",
|
||||
"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",
|
||||
"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.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.Updated": "Oppdatert {time}",
|
||||
"Calculations.Options.average.displayName": "Gjennomsnitt",
|
||||
@ -16,11 +44,142 @@
|
||||
"Calculations.Options.count.displayName": "Antall",
|
||||
"Calculations.Options.count.label": "Antall",
|
||||
"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.label": "Antall Ikke Avmerket",
|
||||
"Calculations.Options.countUnchecked.label": "Antall ikke valgt",
|
||||
"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.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.delete": "Excluir",
|
||||
"BoardComponent.hidden-columns": "Colunas ocultas",
|
||||
@ -16,8 +23,8 @@
|
||||
"BoardMember.unlinkChannel": "Desvincular",
|
||||
"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.",
|
||||
"BoardTemplateSelector.add-template": "Novo modelo",
|
||||
"BoardTemplateSelector.create-empty-board": "Criar board vazio",
|
||||
"BoardTemplateSelector.add-template": "Criar novo modelo",
|
||||
"BoardTemplateSelector.create-empty-board": "Criar um board vazio",
|
||||
"BoardTemplateSelector.delete-template": "Excluir",
|
||||
"BoardTemplateSelector.description": "Adicione um quadro à barra lateral usando qualquer um dos modelos definidos abaixo ou comece do zero.",
|
||||
"BoardTemplateSelector.edit-template": "Editar",
|
||||
@ -25,7 +32,7 @@
|
||||
"BoardTemplateSelector.plugin.no-content-title": "Criar um board",
|
||||
"BoardTemplateSelector.title": "Criar um board",
|
||||
"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.Remainder": "+{remainder} mais",
|
||||
"BoardsUnfurl.Updated": "Atualizado {time}",
|
||||
@ -71,6 +78,7 @@
|
||||
"CardBadges.title-checkboxes": "Caixa de seleção",
|
||||
"CardBadges.title-comments": "Comentários",
|
||||
"CardBadges.title-description": "Este cartão tem uma descrição",
|
||||
"CardDetail.Attach": "Anexar",
|
||||
"CardDetail.Follow": "Seguir",
|
||||
"CardDetail.Following": "Seguindo",
|
||||
"CardDetail.add-content": "Adicionar conteúdo",
|
||||
@ -102,10 +110,13 @@
|
||||
"Categories.CreateCategoryDialog.UpdateText": "Atualizar",
|
||||
"CenterPanel.Login": "Login",
|
||||
"CenterPanel.Share": "Compartilhar",
|
||||
"ChannelIntro.CreateBoard": "Criar um board",
|
||||
"CloudMessage.cloud-server": "Obtenha seu próprio cloud server de graça.",
|
||||
"ColorOption.selectColor": "Selecione {color} Cor",
|
||||
"Comment.delete": "Excluir",
|
||||
"CommentsList.send": "Enviar",
|
||||
"ConfirmPerson.empty": "Vazio",
|
||||
"ConfirmPerson.search": "Buscar...",
|
||||
"ConfirmationDialog.cancel-action": "Cancelar",
|
||||
"ConfirmationDialog.confirm-action": "Confirmar",
|
||||
"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.Title": "Compartilhar quadro",
|
||||
"PersonProperty.board-members": "Membros do Board",
|
||||
"PersonProperty.me": "Eu",
|
||||
"PersonProperty.non-board-members": "Não membros do board",
|
||||
"PropertyMenu.Delete": "Excluir",
|
||||
"PropertyMenu.changeType": "Alterar tipo da propriedade",
|
||||
@ -235,6 +247,7 @@
|
||||
"Sidebar.import-archive": "Importar arquivo",
|
||||
"Sidebar.invite-users": "Convidar usuários",
|
||||
"Sidebar.logout": "Sair",
|
||||
"Sidebar.new-category.badge": "Novo",
|
||||
"Sidebar.no-boards-in-category": "Nenhum board",
|
||||
"Sidebar.product-tour": "Tour pelo produto",
|
||||
"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.Link": "Saiba mais",
|
||||
"SidebarTour.SidebarCategories.Title": "Categorias de barra lateral",
|
||||
"SiteStats.total_boards": "Total de boards",
|
||||
"TableComponent.add-icon": "Adicionar Ícone",
|
||||
"TableComponent.name": "Nome",
|
||||
"TableComponent.plus-new": "+ Novo",
|
||||
@ -267,6 +281,7 @@
|
||||
"TableHeaderMenu.insert-right": "Inserir à direita",
|
||||
"TableHeaderMenu.sort-ascending": "Ordem ascendente",
|
||||
"TableHeaderMenu.sort-descending": "Ordem descendente",
|
||||
"TableRow.MoreOption": "Mais ações",
|
||||
"TableRow.open": "Abrir",
|
||||
"TopBar.give-feedback": "Dar feedback",
|
||||
"URLProperty.copiedLink": "Copiado!",
|
||||
@ -348,6 +363,7 @@
|
||||
"calendar.month": "Mês",
|
||||
"calendar.today": "HOJE",
|
||||
"calendar.week": "Semana",
|
||||
"centerPanel.unknown-user": "Usuário desconhecido",
|
||||
"cloudMessage.learn-more": "Saiba mais",
|
||||
"createImageBlock.failed": "Não foi possível enviar o arquivo. Limite de tamanho alcançado.",
|
||||
"default-properties.badges": "Comentários e descrição",
|
||||
@ -369,6 +385,7 @@
|
||||
"login.log-in-button": "Entrar",
|
||||
"login.log-in-title": "Entrar",
|
||||
"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.contact-link": "notificar seu admin",
|
||||
"notification-box-card-limit-reached.link": "Atualizar para um plano pago",
|
||||
@ -388,14 +405,14 @@
|
||||
"rhs-boards.dm": "DM",
|
||||
"rhs-boards.gm": "GM",
|
||||
"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.link-boards-to-channel": "Vincular boards para {channelName}",
|
||||
"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-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-board1": "Desvincular board Hello",
|
||||
"rhs-boards.unlink-board1": "Desvincular board",
|
||||
"rhs-channel-boards-header.title": "Boards",
|
||||
"share-board.publish": "Publicar",
|
||||
"share-board.share": "Compartilhar",
|
||||
|
@ -2,14 +2,14 @@
|
||||
"AppBar.Tooltip": "Переключить связанные доски",
|
||||
"Attachment.Attachment-title": "Вложение",
|
||||
"AttachmentBlock.DeleteAction": "Удалить",
|
||||
"AttachmentBlock.addElement": "добавить",
|
||||
"AttachmentBlock.delete": "Вложение успешно удалено.",
|
||||
"AttachmentBlock.failed": "Не удалось загрузить файл. Достигнут предел размера вложения.",
|
||||
"AttachmentBlock.addElement": "добавить {type}",
|
||||
"AttachmentBlock.delete": "Вложение удалено.",
|
||||
"AttachmentBlock.failed": "Не удалось загрузить файл, так как превышена квота на размер файла.",
|
||||
"AttachmentBlock.upload": "Загрузка вложения.",
|
||||
"AttachmentBlock.uploadSuccess": "Вложение успешно загружено.",
|
||||
"AttachmentBlock.uploadSuccess": "Вложение загружено.",
|
||||
"AttachmentElement.delete-confirmation-dialog-button-text": "Удалить",
|
||||
"AttachmentElement.download": "Скачать",
|
||||
"AttachmentElement.upload-percentage": "Загрузка",
|
||||
"AttachmentElement.upload-percentage": "Загрузка...({uploadPercent}%)",
|
||||
"BoardComponent.add-a-group": "+ Добавить группу",
|
||||
"BoardComponent.delete": "Удалить",
|
||||
"BoardComponent.hidden-columns": "Скрытые столбцы",
|
||||
@ -31,12 +31,12 @@
|
||||
"BoardTemplateSelector.delete-template": "Удалить",
|
||||
"BoardTemplateSelector.description": "Добавьте доску на боковую панель, используя любой из шаблонов, описанных ниже, или начните с нуля.",
|
||||
"BoardTemplateSelector.edit-template": "Изменить",
|
||||
"BoardTemplateSelector.plugin.no-content-description": "Добавьте доску на боковую панель, используя любой из указанных ниже шаблонов, или начните с нуля.{lineBreak} Участники \"{teamName}\" будут иметь доступ к созданным здесь доскам.",
|
||||
"BoardTemplateSelector.plugin.no-content-description": "Добавьте доску на боковую панель, используя любой из указанных ниже шаблонов, или начните с нуля.",
|
||||
"BoardTemplateSelector.plugin.no-content-title": "Создать доску",
|
||||
"BoardTemplateSelector.title": "Создать доску",
|
||||
"BoardTemplateSelector.use-this-template": "Использовать этот шаблон",
|
||||
"BoardsSwitcher.Title": "Найти доски",
|
||||
"BoardsUnfurl.Limited": "Информация скрыта в связи с тем, что карточка находится в архиве",
|
||||
"BoardsUnfurl.Limited": "Информация скрыта, потому что карточка находится в архиве",
|
||||
"BoardsUnfurl.Remainder": "+{remainder} ещё",
|
||||
"BoardsUnfurl.Updated": "Обновлено {time}",
|
||||
"Calculations.Options.average.displayName": "Среднее",
|
||||
@ -103,9 +103,9 @@
|
||||
"CardDetailProperty.property-deleted": "{propertyName} успешно удалено!",
|
||||
"CardDetailProperty.property-name-change-subtext": "тип из \"{oldPropType}\" в \"{newPropType}\"",
|
||||
"CardDetial.limited-link": "Узнайте больше о наших планах.",
|
||||
"CardDialog.delete-confirmation-dialog-attachment": "Подтвердите удаление вложения!",
|
||||
"CardDialog.delete-confirmation-dialog-attachment": "Подтвердите удаление вложения",
|
||||
"CardDialog.delete-confirmation-dialog-button-text": "Удалить",
|
||||
"CardDialog.delete-confirmation-dialog-heading": "Подтвердите удаление карточки!",
|
||||
"CardDialog.delete-confirmation-dialog-heading": "Подтвердите удаление карточки",
|
||||
"CardDialog.editing-template": "Вы редактируете шаблон.",
|
||||
"CardDialog.nocard": "Эта карточка не существует или недоступна.",
|
||||
"Categories.CreateCategoryDialog.CancelText": "Отмена",
|
||||
@ -114,10 +114,13 @@
|
||||
"Categories.CreateCategoryDialog.UpdateText": "Обновить",
|
||||
"CenterPanel.Login": "Логин",
|
||||
"CenterPanel.Share": "Поделиться",
|
||||
"ChannelIntro.CreateBoard": "Создать доску",
|
||||
"CloudMessage.cloud-server": "Получите свой бесплатный облачный сервер.",
|
||||
"ColorOption.selectColor": "Выберите цвет {color}",
|
||||
"Comment.delete": "Удалить",
|
||||
"CommentsList.send": "Отправить",
|
||||
"ConfirmPerson.empty": "Пусто",
|
||||
"ConfirmPerson.search": "Поиск...",
|
||||
"ConfirmationDialog.cancel-action": "Отмена",
|
||||
"ConfirmationDialog.confirm-action": "Подтвердить",
|
||||
"ContentBlock.Delete": "Удалить",
|
||||
@ -165,6 +168,7 @@
|
||||
"FilterByText.placeholder": "фильтровать текст",
|
||||
"FilterComponent.add-filter": "+ Добавить фильтр",
|
||||
"FilterComponent.delete": "Удалить",
|
||||
"FilterValue.empty": "(пусто)",
|
||||
"FindBoardsDialog.IntroText": "Поиск досок",
|
||||
"FindBoardsDialog.NoResultsFor": "Нет результатов для \"{searchQuery}\"",
|
||||
"FindBoardsDialog.NoResultsSubtext": "Проверьте правильность написания или попробуйте другой запрос.",
|
||||
@ -183,7 +187,7 @@
|
||||
"OnboardingTour.AddComments.Title": "Добавить комментарии",
|
||||
"OnboardingTour.AddDescription.Body": "Добавьте описание к своей карточке, чтобы Ваши коллеги по команде знали, о чем эта карточка.",
|
||||
"OnboardingTour.AddDescription.Title": "Добавить описание",
|
||||
"OnboardingTour.AddProperties.Body": "Добавляйте различные свойства карточкам, чтобы сделать их более мощными!",
|
||||
"OnboardingTour.AddProperties.Body": "Добавляйте различные свойства карточкам, чтобы сделать их более значительными.",
|
||||
"OnboardingTour.AddProperties.Title": "Добавить свойства",
|
||||
"OnboardingTour.AddView.Body": "Перейдите сюда, чтобы создать новый вид для организации доски с использованием различных макетов.",
|
||||
"OnboardingTour.AddView.Title": "Добавить новый вид",
|
||||
@ -284,6 +288,7 @@
|
||||
"TableHeaderMenu.insert-right": "Вставить справа",
|
||||
"TableHeaderMenu.sort-ascending": "Сортировать по возрастанию",
|
||||
"TableHeaderMenu.sort-descending": "Сортировать по убыванию",
|
||||
"TableRow.DuplicateCard": "дублировать карточку",
|
||||
"TableRow.MoreOption": "Больше действий",
|
||||
"TableRow.open": "Открыть",
|
||||
"TopBar.give-feedback": "Дать обратную связь",
|
||||
@ -351,10 +356,13 @@
|
||||
"WelcomePage.Explore.Button": "Исследовать",
|
||||
"WelcomePage.Heading": "Добро пожаловать на Доски",
|
||||
"WelcomePage.NoThanks.Text": "Нет спасибо, сам разберусь",
|
||||
"WelcomePage.StartUsingIt.Text": "Начать пользоваться",
|
||||
"Workspace.editing-board-template": "Вы редактируете шаблон доски.",
|
||||
"badge.guest": "Гость",
|
||||
"boardSelector.confirm-link-board": "Привязать доску к каналу",
|
||||
"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.link": "Ссылка",
|
||||
"boardSelector.search-for-boards": "Поиск досок",
|
||||
@ -363,6 +371,8 @@
|
||||
"calendar.month": "Месяц",
|
||||
"calendar.today": "СЕГОДНЯ",
|
||||
"calendar.week": "Неделя",
|
||||
"centerPanel.undefined": "Отсутствует {propertyName}",
|
||||
"centerPanel.unknown-user": "Неизвестный пользователь",
|
||||
"cloudMessage.learn-more": "Учить больше",
|
||||
"createImageBlock.failed": "Не удалось загрузить файл. Достигнут предел размера файла.",
|
||||
"default-properties.badges": "Комментарии и описание",
|
||||
@ -377,11 +387,15 @@
|
||||
"error.team-undefined": "Не корректная команда.",
|
||||
"error.unknown": "Произошла ошибка.",
|
||||
"generic.previous": "Предыдущий",
|
||||
"imagePaste.upload-failed": "Некоторые файлы не загружены. Достигнут предел размера файла",
|
||||
"imagePaste.upload-failed": "Некоторые файлы не загружены из-за превышения квоты на размер файла.",
|
||||
"limitedCard.title": "Карточки скрыты",
|
||||
"login.log-in-button": "Вход в систему",
|
||||
"login.log-in-title": "Вход в систему",
|
||||
"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.contact-link": "уведомить Вашего администратора",
|
||||
"notification-box-card-limit-reached.link": "Перейти на платный тариф",
|
||||
@ -389,13 +403,18 @@
|
||||
"notification-box-cards-hidden.title": "Это действие скрыло другую карточку",
|
||||
"notification-box.card-limit-reached.not-admin.text": "Чтобы получить доступ к архивным карточкам, Вы можете {contactLink} перейти на платный тариф.",
|
||||
"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.signup-title": "Зарегистрируйте свой аккаунт",
|
||||
"rhs-board-non-admin-msg": "Вы не являетесь администратором этой доски",
|
||||
"rhs-boards.add": "Добавить",
|
||||
"rhs-boards.last-update-at": "Последнее обновление: {datetime}",
|
||||
"rhs-boards.link-boards-to-channel": "Связать доски с {channelName}",
|
||||
"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.unlink-board": "Отвязать доску",
|
||||
"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.delete": "Mazať",
|
||||
"BoardComponent.hidden-columns": "Skryté stľpce",
|
||||
"BoardComponent.delete": "Odstrániť",
|
||||
"BoardComponent.hidden-columns": "Skryté stĺpce",
|
||||
"BoardComponent.hide": "Skryť",
|
||||
"BoardComponent.new": "+ Nový",
|
||||
"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.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.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.Updated": "Upravené {time}",
|
||||
"Calculations.Options.average.displayName": "Priemer",
|
||||
@ -16,13 +42,13 @@
|
||||
"Calculations.Options.count.displayName": "Počet",
|
||||
"Calculations.Options.count.label": "Počet",
|
||||
"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.label": "Spočítaj neoznačené",
|
||||
"Calculations.Options.countUnchecked.label": "Počítať neoznačené",
|
||||
"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.label": "Spočítaj hodnoty",
|
||||
"Calculations.Options.countValue.label": "Počítať hodnotu",
|
||||
"Calculations.Options.dateRange.displayName": "Rozsah",
|
||||
"Calculations.Options.dateRange.label": "Rozsah",
|
||||
"Calculations.Options.earliest.displayName": "Prvý",
|
||||
@ -31,76 +57,130 @@
|
||||
"Calculations.Options.latest.label": "Posledný",
|
||||
"Calculations.Options.max.displayName": "Max",
|
||||
"Calculations.Options.max.label": "Max",
|
||||
"Calculations.Options.median.displayName": "Median",
|
||||
"Calculations.Options.median.label": "Median",
|
||||
"Calculations.Options.median.displayName": "Medián",
|
||||
"Calculations.Options.median.label": "Medián",
|
||||
"Calculations.Options.min.displayName": "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.percentChecked.displayName": "Skontrolované",
|
||||
"Calculations.Options.percentChecked.label": "Percent Skontrolovaných",
|
||||
"Calculations.Options.percentChecked.displayName": "Označené",
|
||||
"Calculations.Options.percentChecked.label": "Percent skontrolovaných",
|
||||
"Calculations.Options.percentUnchecked.displayName": "Neskontrolované",
|
||||
"Calculations.Options.percentUnchecked.label": "Percent neskontrolovaných",
|
||||
"Calculations.Options.range.displayName": "Rozsah",
|
||||
"Calculations.Options.range.label": "Rozsah",
|
||||
"Calculations.Options.sum.displayName": "Súčet",
|
||||
"Calculations.Options.sum.label": "Súčet",
|
||||
"CardDetail.Follow": "Sleduj",
|
||||
"CardDetail.Following": "Sledujúce",
|
||||
"CardDetail.add-content": "Pridaj obsah",
|
||||
"CardDetail.add-icon": "Pridaj ikonu",
|
||||
"CardDetail.add-property": "+ Pridaj vlastnosť",
|
||||
"CardDetail.addCardText": "Pridaj text karty",
|
||||
"CardDetail.moveContent": "presuň obsah karty",
|
||||
"CardDetail.new-comment-placeholder": "Pridaj komentár ...",
|
||||
"CardDetailProperty.confirm-delete-heading": "Potvrď vymazanie vlastnosti",
|
||||
"CardDetailProperty.confirm-delete-subtext": "Skutočne chcete vymazať \"{propertyName}\"? Mazaním ju odstránite zo všetkých kariet na tabuli.",
|
||||
"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.",
|
||||
"CardDetailProperty.confirm-property-type-change": "Potvrď zmenu typu vlastnosti!",
|
||||
"CalendarCard.untitled": "Bez názvu",
|
||||
"CardActionsMenu.copiedLink": "Skopírované!",
|
||||
"CardActionsMenu.copyLink": "Skopírovať odkaz",
|
||||
"CardActionsMenu.delete": "Odstrániť",
|
||||
"CardActionsMenu.duplicate": "Duplikovať",
|
||||
"CardBadges.title-checkboxes": "Začiarkávacie políčka",
|
||||
"CardBadges.title-comments": "Komentáre",
|
||||
"CardBadges.title-description": "Táto karta má popis",
|
||||
"CardDetail.Attach": "Priložiť",
|
||||
"CardDetail.Follow": "Sledovať",
|
||||
"CardDetail.Following": "Sledujúci",
|
||||
"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.property-change-action-button": "Zmeniť vlastnosť",
|
||||
"CardDetailProperty.property-changed": "Zmena vlastnosti úspešná!",
|
||||
"CardDetailProperty.property-deleted": "Mazanie {propertyName} úspešné!",
|
||||
"CardDetailProperty.property-name-change-subtext": "typ od \"{oldPropType}\" do \"{newPropType}\"",
|
||||
"CardDialog.editing-template": "Editujete template.",
|
||||
"CardDialog.nocard": "Karta neexistuje alebo je neprístupná.",
|
||||
"ColorOption.selectColor": "Vyber {color} farbu",
|
||||
"CardDetailProperty.property-deleted": "Odstránenie {propertyName} úspešné!",
|
||||
"CardDetailProperty.property-name-change-subtext": "typ z \"{oldPropType}\" na \"{newPropType}\"",
|
||||
"CardDetial.limited-link": "Dozvedieť sa viac o našich plánoch.",
|
||||
"CardDialog.delete-confirmation-dialog-attachment": "Potvrdiť odstránenie prílohy",
|
||||
"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ť",
|
||||
"CommentsList.send": "Poslať",
|
||||
"CommentsList.send": "Odoslať",
|
||||
"ConfirmPerson.empty": "Prázdne",
|
||||
"ConfirmPerson.search": "Vyhľadať...",
|
||||
"ConfirmationDialog.cancel-action": "Zrušiť",
|
||||
"ConfirmationDialog.confirm-action": "Potvrdiť",
|
||||
"ContentBlock.Delete": "Odstrániť",
|
||||
"ContentBlock.DeleteAction": "Odstrániť",
|
||||
"ContentBlock.addElement": "pridaj {type}",
|
||||
"ContentBlock.checkbox": "checkbox",
|
||||
"ContentBlock.DeleteAction": "odstrániť",
|
||||
"ContentBlock.addElement": "pridať {type}",
|
||||
"ContentBlock.checkbox": "začiarkávacie pole",
|
||||
"ContentBlock.divider": "oddeľovač",
|
||||
"ContentBlock.editCardCheckbox": "označený-checkbox",
|
||||
"ContentBlock.editCardCheckbox": "Začiarknuté pole",
|
||||
"ContentBlock.editCardCheckboxText": "upraviť text karty",
|
||||
"ContentBlock.editCardText": "upraviť text karty",
|
||||
"ContentBlock.editText": "Upraviť text...",
|
||||
"ContentBlock.image": "obrázok",
|
||||
"ContentBlock.insertAbove": "vlož nad",
|
||||
"ContentBlock.moveDown": "Presuň dole",
|
||||
"ContentBlock.moveUp": "Presuň hore",
|
||||
"ContentBlock.insertAbove": "Vložiť nad",
|
||||
"ContentBlock.moveBlock": "presunúť obsah karty",
|
||||
"ContentBlock.moveDown": "Presunúť dole",
|
||||
"ContentBlock.moveUp": "Presunúť hore",
|
||||
"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-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-template": "Naozaj chcete odstrániť nástenkovú šablónu \"{boardTitle}\"?",
|
||||
"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",
|
||||
"Error.mobileweb": "Mobile web support is currently in early beta. Not all functionality may be present.",
|
||||
"Error.websocket-closed": "Websocket pripojenie zlyhalo - prerušené. Skontrolujte konfiguráciu servera ak problém pretrváva.",
|
||||
"Error.mobileweb": "Podpora pre mobilné prehliadače je v skorej bete. Niektoré funkcionality môžu chýbať.",
|
||||
"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.is": "je",
|
||||
"Filter.is-empty": "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-starts-with": "nezačína s",
|
||||
"Filter.starts-with": "začína s",
|
||||
"FilterByText.placeholder": "text filtra",
|
||||
"FilterComponent.add-filter": "+ Pridaj filter",
|
||||
"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",
|
||||
"KanbanCard.untitled": "Nepomenované",
|
||||
"Mutator.new-card-from-template": "nová karta z template-u",
|
||||
"Mutator.new-template-from-card": "nový template z karty",
|
||||
"HideBoard.MenuOption": "Skryť nástenku",
|
||||
"KanbanCard.untitled": "Bez názvu",
|
||||
"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.changeType": "Zmeniť 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.delete": "Xóa",
|
||||
"BoardComponent.hidden-columns": "Cột ẩn",
|
||||
|
4
webapp/package-lock.json
generated
4
webapp/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "focalboard",
|
||||
"version": "7.9.0",
|
||||
"version": "7.10.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "focalboard",
|
||||
"version": "7.9.0",
|
||||
"version": "7.10.0",
|
||||
"dependencies": {
|
||||
"@draft-js-plugins/editor": "^4.1.2",
|
||||
"@draft-js-plugins/emoji": "^4.6.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "focalboard",
|
||||
"version": "7.9.0",
|
||||
"version": "7.10.0",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"scripts": {
|
||||
|
@ -10,7 +10,7 @@ exports[`components/sidebar/GlobalHeader header menu should match snapshot 1`] =
|
||||
/>
|
||||
<a
|
||||
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"
|
||||
target="_blank"
|
||||
>
|
||||
|
@ -51,9 +51,9 @@ exports[`components/sidebarSidebar dont show hidden boards 1`] = `
|
||||
>
|
||||
<div
|
||||
class="version"
|
||||
title="v7.9.0"
|
||||
title="v7.10.0"
|
||||
>
|
||||
v7.9.0
|
||||
v7.10.0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -252,9 +252,9 @@ exports[`components/sidebarSidebar should assign default category if current boa
|
||||
>
|
||||
<div
|
||||
class="version"
|
||||
title="v7.9.0"
|
||||
title="v7.10.0"
|
||||
>
|
||||
v7.9.0
|
||||
v7.10.0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -508,9 +508,9 @@ exports[`components/sidebarSidebar shouldnt do any category assignment is board
|
||||
>
|
||||
<div
|
||||
class="version"
|
||||
title="v7.9.0"
|
||||
title="v7.10.0"
|
||||
>
|
||||
v7.9.0
|
||||
v7.10.0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -919,9 +919,9 @@ exports[`components/sidebarSidebar sidebar hidden 1`] = `
|
||||
>
|
||||
<div
|
||||
class="version"
|
||||
title="v7.9.0"
|
||||
title="v7.10.0"
|
||||
>
|
||||
v7.9.0
|
||||
v7.10.0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1213,9 +1213,9 @@ exports[`components/sidebarSidebar some categories hidden 1`] = `
|
||||
>
|
||||
<div
|
||||
class="version"
|
||||
title="v7.9.0"
|
||||
title="v7.10.0"
|
||||
>
|
||||
v7.9.0
|
||||
v7.10.0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,6 +6,10 @@
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
color: rgba(var(--center-channel-color-rgb));
|
||||
}
|
||||
|
||||
.octo-sidebar-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -203,6 +203,7 @@
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
.MultiPerson.octo-propertyvalue,
|
||||
.Person.octo-propertyvalue,
|
||||
.DateRange.octo-propertyvalue {
|
||||
overflow: unset;
|
||||
|
@ -37,8 +37,8 @@ class Constants {
|
||||
static readonly titleColumnId = '__title'
|
||||
static readonly badgesColumnId = '__badges'
|
||||
|
||||
static readonly versionString = '7.9.0'
|
||||
static readonly versionDisplayString = 'Mar 2023'
|
||||
static readonly versionString = '7.10.0'
|
||||
static readonly versionDisplayString = 'Apr 2023'
|
||||
|
||||
static readonly archiveHelpPage = 'https://docs.mattermost.com/boards/migrate-to-boards.html'
|
||||
static readonly imports = [
|
||||
|
@ -18,6 +18,9 @@ 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.
|
||||
</div>
|
||||
<div
|
||||
class="WelcomePage__content"
|
||||
>
|
||||
<img
|
||||
alt="Boards Welcome Image"
|
||||
class="WelcomePage__image WelcomePage__image--large"
|
||||
@ -28,6 +31,9 @@ exports[`pages/welcome Welcome Page shows Explore Page 1`] = `
|
||||
class="WelcomePage__image WelcomePage__image--small"
|
||||
src="test-file-stub"
|
||||
/>
|
||||
<div
|
||||
class="WelcomePage__buttons"
|
||||
>
|
||||
<button
|
||||
class="Button filled size--large"
|
||||
type="button"
|
||||
@ -47,6 +53,8 @@ exports[`pages/welcome Welcome Page shows Explore Page 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`pages/welcome Welcome Page shows Explore Page with subpath 1`] = `
|
||||
@ -67,6 +75,9 @@ 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.
|
||||
</div>
|
||||
<div
|
||||
class="WelcomePage__content"
|
||||
>
|
||||
<img
|
||||
alt="Boards Welcome Image"
|
||||
class="WelcomePage__image WelcomePage__image--large"
|
||||
@ -77,6 +88,9 @@ exports[`pages/welcome Welcome Page shows Explore Page with subpath 1`] = `
|
||||
class="WelcomePage__image WelcomePage__image--small"
|
||||
src="test-file-stub"
|
||||
/>
|
||||
<div
|
||||
class="WelcomePage__buttons"
|
||||
>
|
||||
<button
|
||||
class="Button filled size--large"
|
||||
type="button"
|
||||
@ -96,4 +110,6 @@ exports[`pages/welcome Welcome Page shows Explore Page with subpath 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -10,6 +10,26 @@
|
||||
@media (max-height: 768px) {
|
||||
justify-content: flex-start;
|
||||
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 {
|
||||
@ -34,7 +54,6 @@
|
||||
}
|
||||
|
||||
.skip {
|
||||
margin-top: 12px;
|
||||
color: rgba(var(--link-color-rgb), 1);
|
||||
cursor: pointer;
|
||||
|
||||
|
@ -127,6 +127,7 @@ const WelcomePage = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='WelcomePage__content'>
|
||||
{/* This image will be rendered on large screens over 2000px */}
|
||||
<img
|
||||
src={Utils.buildURL(BoardWelcomePNG, true)}
|
||||
@ -141,6 +142,7 @@ const WelcomePage = () => {
|
||||
alt='Boards Welcome Image'
|
||||
/>
|
||||
|
||||
<div className='WelcomePage__buttons'>
|
||||
{me?.is_guest !== true &&
|
||||
<Button
|
||||
onClick={startTour}
|
||||
@ -182,6 +184,8 @@ const WelcomePage = () => {
|
||||
</Button>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,23 @@ exports[`properties/dateRange handle clear 1`] = `
|
||||
</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`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -315,4 +315,36 @@ describe('properties/dateRange', () => {
|
||||
|
||||
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.
|
||||
// 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 {DateUtils} from 'react-day-picker'
|
||||
import MomentLocaleUtils from 'react-day-picker/moment'
|
||||
@ -58,6 +58,12 @@ function DateRange(props: PropertyProps): JSX.Element {
|
||||
const [value, setValue] = useState(propertyValue)
|
||||
const intl = useIntl()
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== propertyValue) {
|
||||
setValue(propertyValue)
|
||||
}
|
||||
}, [propertyValue, setValue])
|
||||
|
||||
const onChange = useCallback((newValue) => {
|
||||
if (value !== newValue) {
|
||||
setValue(newValue)
|
||||
|
@ -35,11 +35,14 @@ const URLProperty = (props: PropertyProps): JSX.Element => {
|
||||
if (value !== (props.card.fields.properties[props.propertyTemplate?.id || ''] || '')) {
|
||||
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)
|
||||
if (props.readOnly) {
|
||||
saveTextPropertyRef.current = () => null
|
||||
} else {
|
||||
saveTextPropertyRef.current = saveTextProperty
|
||||
|
||||
}
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
saveTextPropertyRef.current && saveTextPropertyRef.current()
|
||||
|
@ -50,6 +50,7 @@ export const TelemetryActions = {
|
||||
LimitCardLimitReached: 'limit_cardLimitReached',
|
||||
LimitCardLimitLinkOpen: 'limit_cardLimitLinkOpen',
|
||||
VersionMoreInfo: 'version_more_info',
|
||||
ClickChannelsRHSBoard: 'click_board_in_channels_RHS',
|
||||
}
|
||||
|
||||
interface IEventProps {
|
||||
|
Loading…
Reference in New Issue
Block a user