1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-12-21 13:38:56 +02:00

Merge branch 'main' into deduplicate_category_boards

This commit is contained in:
Harshil Sharma 2023-03-09 11:17:24 +05:30
commit 761eaa759b
29 changed files with 586 additions and 178 deletions

View File

@ -6,7 +6,7 @@
"support_url": "https://github.com/mattermost/focalboard/issues", "support_url": "https://github.com/mattermost/focalboard/issues",
"release_notes_url": "https://github.com/mattermost/focalboard/releases", "release_notes_url": "https://github.com/mattermost/focalboard/releases",
"icon_path": "assets/starter-template-icon.svg", "icon_path": "assets/starter-template-icon.svg",
"version": "7.9.0", "version": "7.10.0",
"min_server_version": "7.2.0", "min_server_version": "7.2.0",
"server": { "server": {
"executables": { "executables": {

View File

@ -20,7 +20,7 @@ const manifestStr = `
"support_url": "https://github.com/mattermost/focalboard/issues", "support_url": "https://github.com/mattermost/focalboard/issues",
"release_notes_url": "https://github.com/mattermost/focalboard/releases", "release_notes_url": "https://github.com/mattermost/focalboard/releases",
"icon_path": "assets/starter-template-icon.svg", "icon_path": "assets/starter-template-icon.svg",
"version": "7.9.0", "version": "7.10.0",
"min_server_version": "7.2.0", "min_server_version": "7.2.0",
"server": { "server": {
"executables": { "executables": {

View File

@ -17,8 +17,10 @@ import CompassIcon from '../../../../webapp/src/widgets/icons/compassIcon'
import {Permission} from '../../../../webapp/src/constants' import {Permission} from '../../../../webapp/src/constants'
import './rhsChannelBoardItem.scss'
import BoardPermissionGate from '../../../../webapp/src/components/permissions/boardPermissionGate' import BoardPermissionGate from '../../../../webapp/src/components/permissions/boardPermissionGate'
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../../../webapp/src/telemetry/telemetryClient'
import './rhsChannelBoardItem.scss'
const windowAny = (window as SuiteWindow) const windowAny = (window as SuiteWindow)
@ -36,6 +38,10 @@ const RHSChannelBoardItem = (props: Props) => {
} }
const handleBoardClicked = (boardID: string) => { const handleBoardClicked = (boardID: string) => {
// send the telemetry information for the clicked board
const extraData = {teamID: team.id, board: boardID}
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ClickChannelsRHSBoard, extraData)
window.open(`${windowAny.frontendBaseURL}/team/${team.id}/${boardID}`, '_blank', 'noopener') window.open(`${windowAny.frontendBaseURL}/team/${team.id}/${boardID}`, '_blank', 'noopener')
} }

View File

@ -227,7 +227,7 @@ func jsonStringResponse(w http.ResponseWriter, code int, message string) { //nol
fmt.Fprint(w, message) fmt.Fprint(w, message)
} }
func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) { func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) { //nolint:unparam
setResponseHeader(w, "Content-Type", "application/json") setResponseHeader(w, "Content-Type", "application/json")
w.WriteHeader(code) w.WriteHeader(code)
_, _ = w.Write(json) _, _ = w.Write(json)

View File

@ -123,37 +123,12 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
auditRec.AddMeta("teamID", board.TeamID) auditRec.AddMeta("teamID", board.TeamID)
auditRec.AddMeta("filename", filename) auditRec.AddMeta("filename", filename)
fileInfo, err := a.app.GetFileInfo(filename) fileInfo, fileReader, err := a.app.GetFile(board.TeamID, boardID, filename)
if err != nil && !model.IsErrNotFound(err) { if err != nil && !model.IsErrNotFound(err) {
a.errorResponse(w, r, err) a.errorResponse(w, r, err)
return return
} }
if fileInfo != nil && fileInfo.Archived {
fileMetadata := map[string]interface{}{
"archived": true,
"name": fileInfo.Name,
"size": fileInfo.Size,
"extension": fileInfo.Extension,
}
data, jsonErr := json.Marshal(fileMetadata)
if jsonErr != nil {
a.logger.Error("failed to marshal archived file metadata", mlog.String("filename", filename), mlog.Err(jsonErr))
a.errorResponse(w, r, jsonErr)
return
}
jsonBytesResponse(w, http.StatusBadRequest, data)
return
}
fileReader, err := a.app.GetFileReader(board.TeamID, boardID, filename)
if err != nil && !errors.Is(err, app.ErrFileNotFound) {
a.errorResponse(w, r, err)
return
}
if errors.Is(err, app.ErrFileNotFound) && board.ChannelID != "" { if errors.Is(err, app.ErrFileNotFound) && board.ChannelID != "" {
// prior to moving from workspaces to teams, the filepath was constructed from // prior to moving from workspaces to teams, the filepath was constructed from
// workspaceID, which is the channel ID in plugin mode. // workspaceID, which is the channel ID in plugin mode.

View File

@ -7,6 +7,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/mattermost/focalboard/server/model"
mmModel "github.com/mattermost/mattermost-server/v6/model" mmModel "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/focalboard/server/utils" "github.com/mattermost/focalboard/server/utils"
@ -28,7 +29,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
createdFilename := utils.NewID(utils.IDTypeNone) createdFilename := utils.NewID(utils.IDTypeNone)
fullFilename := fmt.Sprintf(`%s%s`, createdFilename, fileExtension) fullFilename := fmt.Sprintf(`%s%s`, createdFilename, fileExtension)
filePath := filepath.Join(teamID, rootID, fullFilename) filePath := filepath.Join(utils.GetBaseFilePath(), fullFilename)
fileSize, appErr := a.filesBackend.WriteFile(reader, filePath) fileSize, appErr := a.filesBackend.WriteFile(reader, filePath)
if appErr != nil { if appErr != nil {
@ -45,7 +46,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
CreateAt: now, CreateAt: now,
UpdateAt: now, UpdateAt: now,
DeleteAt: 0, DeleteAt: 0,
Path: emptyString, Path: filePath,
ThumbnailPath: emptyString, ThumbnailPath: emptyString,
PreviewPath: emptyString, PreviewPath: emptyString,
Name: filename, Name: filename,
@ -59,6 +60,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
Content: "", Content: "",
RemoteId: nil, RemoteId: nil,
} }
err := a.store.SaveFileInfo(fileInfo) err := a.store.SaveFileInfo(fileInfo)
if err != nil { if err != nil {
return "", err return "", err
@ -77,6 +79,7 @@ func (a *App) GetFileInfo(filename string) (*mmModel.FileInfo, error) {
// will be the fileinfo id. // will be the fileinfo id.
parts := strings.Split(filename, ".") parts := strings.Split(filename, ".")
fileInfoID := parts[0][1:] fileInfoID := parts[0][1:]
fileInfo, err := a.store.GetFileInfo(fileInfoID) fileInfo, err := a.store.GetFileInfo(fileInfoID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -85,6 +88,40 @@ func (a *App) GetFileInfo(filename string) (*mmModel.FileInfo, error) {
return fileInfo, nil return fileInfo, nil
} }
func (a *App) GetFile(teamID, rootID, fileName string) (*mmModel.FileInfo, filestore.ReadCloseSeeker, error) {
fileInfo, err := a.GetFileInfo(fileName)
if err != nil && !model.IsErrNotFound(err) {
a.logger.Error("111")
return nil, nil, err
}
var filePath string
if fileInfo != nil && fileInfo.Path != "" {
filePath = fileInfo.Path
} else {
filePath = filepath.Join(teamID, rootID, fileName)
}
exists, err := a.filesBackend.FileExists(filePath)
if err != nil {
a.logger.Error(fmt.Sprintf("GetFile: Failed to check if file exists as path. Path: %s, error: %e", filePath, err))
return nil, nil, err
}
if !exists {
return nil, nil, ErrFileNotFound
}
reader, err := a.filesBackend.Reader(filePath)
if err != nil {
a.logger.Error(fmt.Sprintf("GetFile: Failed to get file reader of existing file at path: %s, error: %e", filePath, err))
return nil, nil, err
}
return fileInfo, reader, nil
}
func (a *App) GetFileReader(teamID, rootID, filename string) (filestore.ReadCloseSeeker, error) { func (a *App) GetFileReader(teamID, rootID, filename string) (filestore.ReadCloseSeeker, error) {
filePath := filepath.Join(teamID, rootID, filename) filePath := filepath.Join(teamID, rootID, filename)
exists, err := a.filesBackend.FileExists(filePath) exists, err := a.filesBackend.FileExists(filePath)

View File

@ -7,6 +7,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"time"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -195,8 +196,8 @@ func TestSaveFile(t *testing.T) {
writeFileFunc := func(reader io.Reader, path string) int64 { writeFileFunc := func(reader io.Reader, path string) int64 {
paths := strings.Split(path, string(os.PathSeparator)) paths := strings.Split(path, string(os.PathSeparator))
assert.Equal(t, "1", paths[0]) assert.Equal(t, "boards", paths[0])
assert.Equal(t, testBoardID, paths[1]) assert.Equal(t, time.Now().Format("20060102"), paths[1])
fileName = paths[2] fileName = paths[2]
return int64(10) return int64(10)
} }
@ -219,8 +220,8 @@ func TestSaveFile(t *testing.T) {
writeFileFunc := func(reader io.Reader, path string) int64 { writeFileFunc := func(reader io.Reader, path string) int64 {
paths := strings.Split(path, string(os.PathSeparator)) paths := strings.Split(path, string(os.PathSeparator))
assert.Equal(t, "1", paths[0]) assert.Equal(t, "boards", paths[0])
assert.Equal(t, "test-board-id", paths[1]) assert.Equal(t, time.Now().Format("20060102"), paths[1])
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1]) assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
return int64(10) return int64(10)
} }
@ -243,8 +244,8 @@ func TestSaveFile(t *testing.T) {
writeFileFunc := func(reader io.Reader, path string) int64 { writeFileFunc := func(reader io.Reader, path string) int64 {
paths := strings.Split(path, string(os.PathSeparator)) paths := strings.Split(path, string(os.PathSeparator))
assert.Equal(t, "1", paths[0]) assert.Equal(t, "boards", paths[0])
assert.Equal(t, "test-board-id", paths[1]) assert.Equal(t, time.Now().Format("20060102"), paths[1])
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1]) assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
return int64(10) return int64(10)
} }
@ -304,3 +305,80 @@ func TestGetFileInfo(t *testing.T) {
assert.Nil(t, fetchedFileInfo) assert.Nil(t, fetchedFileInfo)
}) })
} }
func TestGetFile(t *testing.T) {
th, _ := SetupTestHelper(t)
t.Run("when FileInfo exists", func(t *testing.T) {
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(&mmModel.FileInfo{
Id: "fileInfoID",
Path: "/path/to/file/fileName.txt",
}, nil)
mockedFileBackend := &mocks.FileBackend{}
th.App.filesBackend = mockedFileBackend
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
readerFunc := func(path string) filestore.ReadCloseSeeker {
return mockedReadCloseSeek
}
readerErrorFunc := func(path string) error {
return nil
}
mockedFileBackend.On("Reader", "/path/to/file/fileName.txt").Return(readerFunc, readerErrorFunc)
mockedFileBackend.On("FileExists", "/path/to/file/fileName.txt").Return(true, nil)
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
assert.NoError(t, err)
assert.NotNil(t, fileInfo)
assert.NotNil(t, seeker)
})
t.Run("when FileInfo doesn't exist", func(t *testing.T) {
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(nil, nil)
mockedFileBackend := &mocks.FileBackend{}
th.App.filesBackend = mockedFileBackend
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
readerFunc := func(path string) filestore.ReadCloseSeeker {
return mockedReadCloseSeek
}
readerErrorFunc := func(path string) error {
return nil
}
mockedFileBackend.On("Reader", "teamID/boardID/7fileInfoID.txt").Return(readerFunc, readerErrorFunc)
mockedFileBackend.On("FileExists", "teamID/boardID/7fileInfoID.txt").Return(true, nil)
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
assert.NoError(t, err)
assert.Nil(t, fileInfo)
assert.NotNil(t, seeker)
})
t.Run("when FileInfo exists but FileInfo.Path is not set", func(t *testing.T) {
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(&mmModel.FileInfo{
Id: "fileInfoID",
Path: "",
}, nil)
mockedFileBackend := &mocks.FileBackend{}
th.App.filesBackend = mockedFileBackend
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
readerFunc := func(path string) filestore.ReadCloseSeeker {
return mockedReadCloseSeek
}
readerErrorFunc := func(path string) error {
return nil
}
mockedFileBackend.On("Reader", "teamID/boardID/7fileInfoID.txt").Return(readerFunc, readerErrorFunc)
mockedFileBackend.On("FileExists", "teamID/boardID/7fileInfoID.txt").Return(true, nil)
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
assert.NoError(t, err)
assert.NotNil(t, fileInfo)
assert.NotNil(t, seeker)
})
}

View File

@ -380,6 +380,8 @@ func (c *Client) GetCards(boardID string, page int, perPage int) ([]*model.Card,
return nil, BuildErrorResponse(r, err) return nil, BuildErrorResponse(r, err)
} }
defer closeBody(r)
var cards []*model.Card var cards []*model.Card
if err := json.NewDecoder(r.Body).Decode(&cards); err != nil { if err := json.NewDecoder(r.Body).Decode(&cards); err != nil {
return nil, BuildErrorResponse(r, err) return nil, BuildErrorResponse(r, err)
@ -398,6 +400,8 @@ func (c *Client) PatchCard(cardID string, cardPatch *model.CardPatch, disableNot
return nil, BuildErrorResponse(r, err) return nil, BuildErrorResponse(r, err)
} }
defer closeBody(r)
var cardNew *model.Card var cardNew *model.Card
if err := json.NewDecoder(r.Body).Decode(&cardNew); err != nil { if err := json.NewDecoder(r.Body).Decode(&cardNew); err != nil {
return nil, BuildErrorResponse(r, err) return nil, BuildErrorResponse(r, err)
@ -412,6 +416,8 @@ func (c *Client) GetCard(cardID string) (*model.Card, *Response) {
return nil, BuildErrorResponse(r, err) return nil, BuildErrorResponse(r, err)
} }
defer closeBody(r)
var card *model.Card var card *model.Card
if err := json.NewDecoder(r.Body).Decode(&card); err != nil { if err := json.NewDecoder(r.Body).Decode(&card); err != nil {
return nil, BuildErrorResponse(r, err) return nil, BuildErrorResponse(r, err)
@ -450,6 +456,7 @@ func (c *Client) DeleteCategory(teamID, categoryID string) *Response {
return BuildErrorResponse(r, err) return BuildErrorResponse(r, err)
} }
defer closeBody(r)
return BuildResponse(r) return BuildResponse(r)
} }
@ -1049,6 +1056,7 @@ func (c *Client) HideBoard(teamID, categoryID, boardID string) *Response {
return BuildErrorResponse(r, err) return BuildErrorResponse(r, err)
} }
defer closeBody(r)
return BuildResponse(r) return BuildResponse(r)
} }
@ -1058,5 +1066,6 @@ func (c *Client) UnhideBoard(teamID, categoryID, boardID string) *Response {
return BuildErrorResponse(r, err) return BuildErrorResponse(r, err)
} }
defer closeBody(r)
return BuildResponse(r) return BuildResponse(r)
} }

View File

@ -8,6 +8,7 @@ import (
// It should be maintained in chronological order with most current // It should be maintained in chronological order with most current
// release at the front of the list. // release at the front of the list.
var versions = []string{ var versions = []string{
"7.10.0",
"7.9.0", "7.9.0",
"7.8.0", "7.8.0",
"7.7.0", "7.7.0",

View File

@ -22,6 +22,7 @@ func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) er
"extension", "extension",
"size", "size",
"delete_at", "delete_at",
"path",
"archived", "archived",
). ).
Values( Values(
@ -31,6 +32,7 @@ func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) er
fileInfo.Extension, fileInfo.Extension,
fileInfo.Size, fileInfo.Size,
fileInfo.DeleteAt, fileInfo.DeleteAt,
fileInfo.Path,
false, false,
) )
@ -57,6 +59,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo,
"extension", "extension",
"size", "size",
"archived", "archived",
"path",
). ).
From(s.tablePrefix + "file_info"). From(s.tablePrefix + "file_info").
Where(sq.Eq{"Id": id}) Where(sq.Eq{"Id": id})
@ -73,6 +76,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo,
&fileInfo.Extension, &fileInfo.Extension,
&fileInfo.Size, &fileInfo.Size,
&fileInfo.Archived, &fileInfo.Archived,
&fileInfo.Path,
) )
if err != nil { if err != nil {

View File

@ -0,0 +1 @@
{{ addColumnIfNeeded "file_info" "path" "varchar(512)" "" }}

View File

@ -2,6 +2,7 @@ package utils
import ( import (
"encoding/json" "encoding/json"
"path"
"reflect" "reflect"
"time" "time"
@ -120,3 +121,7 @@ func DedupeStringArr(arr []string) []string {
return dedupedArr return dedupedArr
} }
func GetBaseFilePath() string {
return path.Join("boards", time.Now().Format("20060102"))
}

View File

@ -1,14 +1,42 @@
{ {
"AppBar.Tooltip": "Veksle lenkede tavler",
"Attachment.Attachment-title": "Vedlegg",
"AttachmentBlock.DeleteAction": "slett",
"AttachmentBlock.addElement": "legg til {type}",
"AttachmentBlock.delete": "Vedlegg slettet.",
"AttachmentBlock.failed": "Denne filen kunne ikke lastes opp fordi størrelsesgrensen er nådd.",
"AttachmentBlock.upload": "Vedlegg lastes opp.",
"AttachmentBlock.uploadSuccess": "Vedlegg lastet opp.",
"AttachmentElement.delete-confirmation-dialog-button-text": "Slett",
"AttachmentElement.download": "Last ned",
"AttachmentElement.upload-percentage": "Laster opp ...({uploadPercent}%)",
"BoardComponent.add-a-group": "+ Legg til gruppe", "BoardComponent.add-a-group": "+ Legg til gruppe",
"BoardComponent.delete": "Slett", "BoardComponent.delete": "Slett",
"BoardComponent.hidden-columns": "Skjulte kolonner", "BoardComponent.hidden-columns": "Skjulte kolonner",
"BoardComponent.hide": "Skjul", "BoardComponent.hide": "Skjul",
"BoardComponent.new": "+ Ny", "BoardComponent.new": "+ Ny",
"BoardComponent.no-property": "Ingen {property}", "BoardComponent.no-property": "Ingen {property}",
"BoardComponent.no-property-title": "Elementer med en tom {property} område kommer hit. Denne kolonnen kan ikke fjernes.", "BoardComponent.no-property-title": "Elementer med tom {property} atributt legges her. Denne kolonnen kan ikke fjernes.",
"BoardComponent.show": "Vis", "BoardComponent.show": "Vis",
"BoardMember.schemeAdmin": "Admin",
"BoardMember.schemeCommenter": "Kommentator",
"BoardMember.schemeEditor": "Redaktør",
"BoardMember.schemeNone": "Ingen",
"BoardMember.schemeViewer": "Viser",
"BoardMember.unlinkChannel": "Fjern lenke",
"BoardPage.newVersion": "En ny versjon av Boards er tilgjengelig, klikk her for å laste inn på nytt.", "BoardPage.newVersion": "En ny versjon av Boards er tilgjengelig, klikk her for å laste inn på nytt.",
"BoardPage.syncFailed": "Tavle kan slettes eller adgangen trekkes tilbake.", "BoardPage.syncFailed": "Tavle kan slettes eller adgangen trekkes tilbake.",
"BoardTemplateSelector.add-template": "Lag ny mal",
"BoardTemplateSelector.create-empty-board": "Opprett tom tavle",
"BoardTemplateSelector.delete-template": "Slett",
"BoardTemplateSelector.description": "Legg til en tavle til sidestolpen med hvilken mal du vil fra listen under, eller start med en helt tom tavle.",
"BoardTemplateSelector.edit-template": "Rediger",
"BoardTemplateSelector.plugin.no-content-description": "Legg til en tavle i sidestolpen med hvilken mal du vil, eller start med en tom tavle.",
"BoardTemplateSelector.plugin.no-content-title": "Lag ny tavle",
"BoardTemplateSelector.title": "Lag ny tavle",
"BoardTemplateSelector.use-this-template": "Bruk denne malen",
"BoardsSwitcher.Title": "Finn tavle",
"BoardsUnfurl.Limited": "Flere detaljer er skjult fordi kortet er arkivert",
"BoardsUnfurl.Remainder": "+{remainder} mer", "BoardsUnfurl.Remainder": "+{remainder} mer",
"BoardsUnfurl.Updated": "Oppdatert {time}", "BoardsUnfurl.Updated": "Oppdatert {time}",
"Calculations.Options.average.displayName": "Gjennomsnitt", "Calculations.Options.average.displayName": "Gjennomsnitt",
@ -16,11 +44,142 @@
"Calculations.Options.count.displayName": "Antall", "Calculations.Options.count.displayName": "Antall",
"Calculations.Options.count.label": "Antall", "Calculations.Options.count.label": "Antall",
"Calculations.Options.countChecked.displayName": "Avkrysset", "Calculations.Options.countChecked.displayName": "Avkrysset",
"Calculations.Options.countChecked.label": "Antall avsjekket", "Calculations.Options.countChecked.label": "Antall valgt",
"Calculations.Options.countUnchecked.displayName": "Ikke avmerket", "Calculations.Options.countUnchecked.displayName": "Ikke avmerket",
"Calculations.Options.countUnchecked.label": "Antall Ikke Avmerket", "Calculations.Options.countUnchecked.label": "Antall ikke valgt",
"Calculations.Options.countUniqueValue.displayName": "Unik", "Calculations.Options.countUniqueValue.displayName": "Unik",
"Calculations.Options.countUniqueValue.label": "Antall Unike Verdier", "Calculations.Options.countUniqueValue.label": "Antall unike verdier",
"Calculations.Options.countValue.displayName": "Verdier", "Calculations.Options.countValue.displayName": "Verdier",
"Calculations.Options.countValue.label": "Antall Verdier" "Calculations.Options.countValue.label": "Antall verdier",
"Calculations.Options.dateRange.displayName": "Tidsrom",
"Calculations.Options.dateRange.label": "Tidsrom",
"Calculations.Options.earliest.displayName": "Tiligst",
"Calculations.Options.earliest.label": "Tiligst",
"Calculations.Options.latest.displayName": "Senest",
"Calculations.Options.latest.label": "Senest",
"Calculations.Options.max.displayName": "Maks",
"Calculations.Options.max.label": "Maks",
"Calculations.Options.median.displayName": "Median",
"Calculations.Options.median.label": "Median",
"Calculations.Options.min.displayName": "Min",
"Calculations.Options.min.label": "Min",
"Calculations.Options.none.displayName": "Kalkulèr",
"Calculations.Options.none.label": "Ingen",
"Calculations.Options.percentChecked.displayName": "Valgt",
"Calculations.Options.percentChecked.label": "Prosent valgt",
"Calculations.Options.percentUnchecked.displayName": "Ikke valgt",
"Calculations.Options.percentUnchecked.label": "Prosent ikke valgt",
"Calculations.Options.range.displayName": "Tidsrom",
"Calculations.Options.range.label": "Tidsrom",
"Calculations.Options.sum.displayName": "Sum",
"Calculations.Options.sum.label": "Sum",
"CalendarCard.untitled": "Uten navn",
"CardActionsMenu.copiedLink": "Kopiert!",
"CardActionsMenu.copyLink": "Kopier lenke",
"CardActionsMenu.delete": "Slett",
"CardActionsMenu.duplicate": "Dupliser",
"CardBadges.title-checkboxes": "Avkrysningsbokser",
"CardBadges.title-comments": "Kommentarer",
"CardBadges.title-description": "Dette kortet har en beskrivelsestekst",
"CardDetail.Attach": "Legg ved",
"CardDetail.Follow": "Følg",
"CardDetail.Following": "Følger",
"CardDetail.add-content": "Legg til innhold",
"CardDetail.add-icon": "Legg til ikon",
"CardDetail.add-property": "+ Legg til en verdi",
"CardDetail.addCardText": "legg inn tekst i kortet",
"CardDetail.limited-body": "Oppgrader til vår profesjonelle eller bedriftsplan.",
"CardDetail.limited-button": "Oppgrader",
"CardDetail.limited-title": "Dette kortet er skjult",
"CardDetail.moveContent": "Flytt innholdet",
"CardDetail.new-comment-placeholder": "Legg til kommentar ...",
"CardDetailProperty.confirm-delete-heading": "Bekreft sletting av verdi",
"CardDetailProperty.confirm-delete-subtext": "Er du sikker på at du vil slette verdien \"{propertyName}\"? Dette vil fjerne verdien fra alle kortene på denne tavlen.",
"CardDetailProperty.confirm-property-name-change-subtext": "Er du sikker på at du vil endre verdien \"{propertyName}\" {customText}? Dette vil påvirke verdien på {numOfCards} kort på denne tavlen, og kan forårsake at du mister informasjon.",
"CardDetailProperty.confirm-property-type-change": "Bekreft endring av verditype",
"CardDetailProperty.delete-action-button": "Slett",
"CardDetailProperty.property-change-action-button": "Endre verdi",
"CardDetailProperty.property-changed": "Verdi endret!",
"CardDetailProperty.property-deleted": "Fjernet {propertyName}!",
"CardDetailProperty.property-name-change-subtext": "type fra \"{oldPropType}\" til \"{newPropType}\"",
"CardDetial.limited-link": "Lær mer om våre planer.",
"CardDialog.delete-confirmation-dialog-attachment": "Bekreft sletting av vedlegg",
"CardDialog.delete-confirmation-dialog-button-text": "Slett",
"CardDialog.delete-confirmation-dialog-heading": "Bekreft sletting av kort",
"CardDialog.editing-template": "Du redigerer en mal.",
"CardDialog.nocard": "Dette kortet eksisterer ikke eller du har ikke tilgang.",
"Categories.CreateCategoryDialog.CancelText": "Avbryt",
"Categories.CreateCategoryDialog.CreateText": "Opprett",
"Categories.CreateCategoryDialog.Placeholder": "Navngi kategorien",
"Categories.CreateCategoryDialog.UpdateText": "Oppdater",
"CenterPanel.Login": "Logg inn",
"CenterPanel.Share": "Del",
"ChannelIntro.CreateBoard": "Opprett tavle",
"CloudMessage.cloud-server": "Få din egen gratis skytjener.",
"ColorOption.selectColor": "Velg {color} farge",
"Comment.delete": "Slett",
"CommentsList.send": "Send",
"ConfirmPerson.empty": "Tom",
"ConfirmPerson.search": "Søk ...",
"ConfirmationDialog.cancel-action": "Avbryt",
"ConfirmationDialog.confirm-action": "Bekreft",
"ContentBlock.Delete": "Slett",
"ContentBlock.DeleteAction": "slett",
"ContentBlock.addElement": "legg til {type}",
"ContentBlock.checkbox": "avkrysningsboks",
"ContentBlock.divider": "avdeler",
"ContentBlock.editCardCheckbox": "krysset-boks",
"ContentBlock.editCardCheckboxText": "rediger kort tekst",
"ContentBlock.editCardText": "rediger kort tekst",
"ContentBlock.editText": "Rediger tekst ...",
"ContentBlock.image": "bilde",
"ContentBlock.insertAbove": "Sett inn over",
"ContentBlock.moveBlock": "flytt kort innhold",
"ContentBlock.moveDown": "Flytt ned",
"ContentBlock.moveUp": "Flytt opp",
"ContentBlock.text": "tekst",
"DateRange.clear": "Tøm",
"DateRange.empty": "Tom",
"DateRange.endDate": "Sluttdato",
"DateRange.today": "I dag",
"DeleteBoardDialog.confirm-cancel": "Avbryt",
"DeleteBoardDialog.confirm-delete": "Slett",
"DeleteBoardDialog.confirm-info": "Er du sikker på at du vil slette tavlen \"{boardTitle}\"? Dette vil slette alle kortene på tavlen.",
"DeleteBoardDialog.confirm-info-template": "Er du sikker på at du vil slette tavlemalen \"{boardTitle}\"?",
"DeleteBoardDialog.confirm-tite": "Bekreft sletting av tavle",
"DeleteBoardDialog.confirm-tite-template": "Bekreft sletting av tavlemal",
"Dialog.closeDialog": "Lukk",
"EditableDayPicker.today": "I dag",
"Error.mobileweb": "Støtte for bruk i nettleser på mobil er i tidlig beta. Alt vil ikke fungere.",
"Error.websocket-closed": "Problemer med kobling til tjeneren. Sjekk konfigurasjonen hvis problemet vedvarer.",
"Filter.contains": "inneholder",
"Filter.ends-with": "ender med",
"Filter.includes": "inkluderer",
"Filter.is": "er",
"Filter.is-empty": "er tom",
"Filter.is-not-empty": "er ikke tom",
"Filter.is-not-set": "er ikke satt",
"Filter.is-set": "er satt",
"Filter.not-contains": "inkluderer ikke",
"Filter.not-ends-with": "ender ikke med",
"Filter.not-includes": "inkluderer ikke",
"Filter.not-starts-with": "starter ikke med",
"Filter.starts-with": "starter med",
"FilterByText.placeholder": "filtrer tekst",
"FilterComponent.add-filter": "+ Nytt filter",
"FilterComponent.delete": "Slett",
"FilterValue.empty": "(tom)",
"FindBoardsDialog.IntroText": "Søk etter tavle",
"FindBoardsDialog.NoResultsFor": "Ingen resultat for \"{searchQuery}\"",
"FindBoardsDialog.NoResultsSubtext": "Sjekk stavingen eller søk på noe annet.",
"FindBoardsDialog.SubTitle": "Skriv for å finne en tavle. Bruk <b>opp/ned</b> for å navigere. <b>Enter</b> for å velge, eller <b>Esc</b> for å avbryte",
"FindBoardsDialog.Title": "Finn tavle",
"GroupBy.hideEmptyGroups": "Skjul {count} tomme grupper",
"GroupBy.showHiddenGroups": "Vis {count} tomme grupper",
"GroupBy.ungroup": "Fjern fra gruppe",
"HideBoard.MenuOption": "Skjul tavlen",
"KanbanCard.untitled": "Uten navn",
"Mutator.new-board-from-template": "ny tavle fra mal",
"Mutator.new-card-from-template": "nytt kort fra mal",
"Mutator.new-template-from-card": "ny mal fra kort"
} }

View File

@ -1,5 +1,12 @@
{ {
"AppBar.Tooltip": "Ativar Boards vinculados", "AppBar.Tooltip": "Ativar boards vinculados",
"Attachment.Attachment-title": "Anexo",
"AttachmentBlock.DeleteAction": "apagar",
"AttachmentBlock.addElement": "adicionar {type}",
"AttachmentBlock.delete": "Anexo apagado.",
"AttachmentBlock.uploadSuccess": "Anexo enviado.",
"AttachmentElement.delete-confirmation-dialog-button-text": "Apagar",
"AttachmentElement.upload-percentage": "Enviando...({uploadPercent}%)",
"BoardComponent.add-a-group": "+ Adicione um grupo", "BoardComponent.add-a-group": "+ Adicione um grupo",
"BoardComponent.delete": "Excluir", "BoardComponent.delete": "Excluir",
"BoardComponent.hidden-columns": "Colunas ocultas", "BoardComponent.hidden-columns": "Colunas ocultas",
@ -16,8 +23,8 @@
"BoardMember.unlinkChannel": "Desvincular", "BoardMember.unlinkChannel": "Desvincular",
"BoardPage.newVersion": "Uma nova versão do Boards está disponível, clique aqui para recarregar.", "BoardPage.newVersion": "Uma nova versão do Boards está disponível, clique aqui para recarregar.",
"BoardPage.syncFailed": "O Board pode ter sido excluído ou o acesso revogado.", "BoardPage.syncFailed": "O Board pode ter sido excluído ou o acesso revogado.",
"BoardTemplateSelector.add-template": "Novo modelo", "BoardTemplateSelector.add-template": "Criar novo modelo",
"BoardTemplateSelector.create-empty-board": "Criar board vazio", "BoardTemplateSelector.create-empty-board": "Criar um board vazio",
"BoardTemplateSelector.delete-template": "Excluir", "BoardTemplateSelector.delete-template": "Excluir",
"BoardTemplateSelector.description": "Adicione um quadro à barra lateral usando qualquer um dos modelos definidos abaixo ou comece do zero.", "BoardTemplateSelector.description": "Adicione um quadro à barra lateral usando qualquer um dos modelos definidos abaixo ou comece do zero.",
"BoardTemplateSelector.edit-template": "Editar", "BoardTemplateSelector.edit-template": "Editar",
@ -25,7 +32,7 @@
"BoardTemplateSelector.plugin.no-content-title": "Criar um board", "BoardTemplateSelector.plugin.no-content-title": "Criar um board",
"BoardTemplateSelector.title": "Criar um board", "BoardTemplateSelector.title": "Criar um board",
"BoardTemplateSelector.use-this-template": "Use este template", "BoardTemplateSelector.use-this-template": "Use este template",
"BoardsSwitcher.Title": "Encontrar Boards", "BoardsSwitcher.Title": "Encontrar boards",
"BoardsUnfurl.Limited": "Detalhes adicionais estão ocultos devido ao cartão ter sido arquivado", "BoardsUnfurl.Limited": "Detalhes adicionais estão ocultos devido ao cartão ter sido arquivado",
"BoardsUnfurl.Remainder": "+{remainder} mais", "BoardsUnfurl.Remainder": "+{remainder} mais",
"BoardsUnfurl.Updated": "Atualizado {time}", "BoardsUnfurl.Updated": "Atualizado {time}",
@ -71,6 +78,7 @@
"CardBadges.title-checkboxes": "Caixa de seleção", "CardBadges.title-checkboxes": "Caixa de seleção",
"CardBadges.title-comments": "Comentários", "CardBadges.title-comments": "Comentários",
"CardBadges.title-description": "Este cartão tem uma descrição", "CardBadges.title-description": "Este cartão tem uma descrição",
"CardDetail.Attach": "Anexar",
"CardDetail.Follow": "Seguir", "CardDetail.Follow": "Seguir",
"CardDetail.Following": "Seguindo", "CardDetail.Following": "Seguindo",
"CardDetail.add-content": "Adicionar conteúdo", "CardDetail.add-content": "Adicionar conteúdo",
@ -102,10 +110,13 @@
"Categories.CreateCategoryDialog.UpdateText": "Atualizar", "Categories.CreateCategoryDialog.UpdateText": "Atualizar",
"CenterPanel.Login": "Login", "CenterPanel.Login": "Login",
"CenterPanel.Share": "Compartilhar", "CenterPanel.Share": "Compartilhar",
"ChannelIntro.CreateBoard": "Criar um board",
"CloudMessage.cloud-server": "Obtenha seu próprio cloud server de graça.", "CloudMessage.cloud-server": "Obtenha seu próprio cloud server de graça.",
"ColorOption.selectColor": "Selecione {color} Cor", "ColorOption.selectColor": "Selecione {color} Cor",
"Comment.delete": "Excluir", "Comment.delete": "Excluir",
"CommentsList.send": "Enviar", "CommentsList.send": "Enviar",
"ConfirmPerson.empty": "Vazio",
"ConfirmPerson.search": "Buscar...",
"ConfirmationDialog.cancel-action": "Cancelar", "ConfirmationDialog.cancel-action": "Cancelar",
"ConfirmationDialog.confirm-action": "Confirmar", "ConfirmationDialog.confirm-action": "Confirmar",
"ContentBlock.Delete": "Excluir", "ContentBlock.Delete": "Excluir",
@ -181,6 +192,7 @@
"OnboardingTour.ShareBoard.Body": "Você pode compartilhar seu board internament, com seu time, ou public para permitir visibilidade fora da sua organização.", "OnboardingTour.ShareBoard.Body": "Você pode compartilhar seu board internament, com seu time, ou public para permitir visibilidade fora da sua organização.",
"OnboardingTour.ShareBoard.Title": "Compartilhar quadro", "OnboardingTour.ShareBoard.Title": "Compartilhar quadro",
"PersonProperty.board-members": "Membros do Board", "PersonProperty.board-members": "Membros do Board",
"PersonProperty.me": "Eu",
"PersonProperty.non-board-members": "Não membros do board", "PersonProperty.non-board-members": "Não membros do board",
"PropertyMenu.Delete": "Excluir", "PropertyMenu.Delete": "Excluir",
"PropertyMenu.changeType": "Alterar tipo da propriedade", "PropertyMenu.changeType": "Alterar tipo da propriedade",
@ -235,6 +247,7 @@
"Sidebar.import-archive": "Importar arquivo", "Sidebar.import-archive": "Importar arquivo",
"Sidebar.invite-users": "Convidar usuários", "Sidebar.invite-users": "Convidar usuários",
"Sidebar.logout": "Sair", "Sidebar.logout": "Sair",
"Sidebar.new-category.badge": "Novo",
"Sidebar.no-boards-in-category": "Nenhum board", "Sidebar.no-boards-in-category": "Nenhum board",
"Sidebar.product-tour": "Tour pelo produto", "Sidebar.product-tour": "Tour pelo produto",
"Sidebar.random-icons": "Ícones aleatórios", "Sidebar.random-icons": "Ícones aleatórios",
@ -257,6 +270,7 @@
"SidebarTour.SidebarCategories.Body": "Todos seus boards agora são organizados sob sua nova barra lateral. Não é mais necessárioa alternar entre espaços de trabalho. Categorias personalizadas em suas estações prévias de trabalho foram automaticamente criadas para você como parte do seu upgrade para v7.2. Estas podem ser removidas ou editadas de acordo com a sua preferência.", "SidebarTour.SidebarCategories.Body": "Todos seus boards agora são organizados sob sua nova barra lateral. Não é mais necessárioa alternar entre espaços de trabalho. Categorias personalizadas em suas estações prévias de trabalho foram automaticamente criadas para você como parte do seu upgrade para v7.2. Estas podem ser removidas ou editadas de acordo com a sua preferência.",
"SidebarTour.SidebarCategories.Link": "Saiba mais", "SidebarTour.SidebarCategories.Link": "Saiba mais",
"SidebarTour.SidebarCategories.Title": "Categorias de barra lateral", "SidebarTour.SidebarCategories.Title": "Categorias de barra lateral",
"SiteStats.total_boards": "Total de boards",
"TableComponent.add-icon": "Adicionar Ícone", "TableComponent.add-icon": "Adicionar Ícone",
"TableComponent.name": "Nome", "TableComponent.name": "Nome",
"TableComponent.plus-new": "+ Novo", "TableComponent.plus-new": "+ Novo",
@ -267,6 +281,7 @@
"TableHeaderMenu.insert-right": "Inserir à direita", "TableHeaderMenu.insert-right": "Inserir à direita",
"TableHeaderMenu.sort-ascending": "Ordem ascendente", "TableHeaderMenu.sort-ascending": "Ordem ascendente",
"TableHeaderMenu.sort-descending": "Ordem descendente", "TableHeaderMenu.sort-descending": "Ordem descendente",
"TableRow.MoreOption": "Mais ações",
"TableRow.open": "Abrir", "TableRow.open": "Abrir",
"TopBar.give-feedback": "Dar feedback", "TopBar.give-feedback": "Dar feedback",
"URLProperty.copiedLink": "Copiado!", "URLProperty.copiedLink": "Copiado!",
@ -348,6 +363,7 @@
"calendar.month": "Mês", "calendar.month": "Mês",
"calendar.today": "HOJE", "calendar.today": "HOJE",
"calendar.week": "Semana", "calendar.week": "Semana",
"centerPanel.unknown-user": "Usuário desconhecido",
"cloudMessage.learn-more": "Saiba mais", "cloudMessage.learn-more": "Saiba mais",
"createImageBlock.failed": "Não foi possível enviar o arquivo. Limite de tamanho alcançado.", "createImageBlock.failed": "Não foi possível enviar o arquivo. Limite de tamanho alcançado.",
"default-properties.badges": "Comentários e descrição", "default-properties.badges": "Comentários e descrição",
@ -369,6 +385,7 @@
"login.log-in-button": "Entrar", "login.log-in-button": "Entrar",
"login.log-in-title": "Entrar", "login.log-in-title": "Entrar",
"login.register-button": "ou criar uma conta se você ainda não tiver uma", "login.register-button": "ou criar uma conta se você ainda não tiver uma",
"new_channel_modal.create_board.select_template_placeholder": "Selecionar um modelo",
"notification-box-card-limit-reached.close-tooltip": "Soneca por 10 dias", "notification-box-card-limit-reached.close-tooltip": "Soneca por 10 dias",
"notification-box-card-limit-reached.contact-link": "notificar seu admin", "notification-box-card-limit-reached.contact-link": "notificar seu admin",
"notification-box-card-limit-reached.link": "Atualizar para um plano pago", "notification-box-card-limit-reached.link": "Atualizar para um plano pago",
@ -388,14 +405,14 @@
"rhs-boards.dm": "DM", "rhs-boards.dm": "DM",
"rhs-boards.gm": "GM", "rhs-boards.gm": "GM",
"rhs-boards.header.dm": "esta Direct Message", "rhs-boards.header.dm": "esta Direct Message",
"rhs-boards.header.gm": "Este Gruop Message", "rhs-boards.header.gm": "esta mensagem de grupo",
"rhs-boards.last-update-at": "Última atualização em: {datetime}", "rhs-boards.last-update-at": "Última atualização em: {datetime}",
"rhs-boards.link-boards-to-channel": "Vincular boards para {channelName}", "rhs-boards.link-boards-to-channel": "Vincular boards para {channelName}",
"rhs-boards.linked-boards": "Boards vinculados", "rhs-boards.linked-boards": "Boards vinculados",
"rhs-boards.no-boards-linked-to-channel": "Nenhum board está vinculado a {channelName} ainda", "rhs-boards.no-boards-linked-to-channel": "Nenhum board está vinculado a {channelName} ainda",
"rhs-boards.no-boards-linked-to-channel-description": "Boards é uma ferramenta de gerenciamento de projeto que ajuda a definir, organizar, rastrear e gerenciar o trabalho entre times, usando uma visualização de quadro estilo Kaban familiar.", "rhs-boards.no-boards-linked-to-channel-description": "Boards é uma ferramenta de gerenciamento de projeto que ajuda a definir, organizar, rastrear e gerenciar o trabalho entre times, usando uma visualização de quadro estilo Kaban familiar.",
"rhs-boards.unlink-board": "Desvincular board", "rhs-boards.unlink-board": "Desvincular board",
"rhs-boards.unlink-board1": "Desvincular board Hello", "rhs-boards.unlink-board1": "Desvincular board",
"rhs-channel-boards-header.title": "Boards", "rhs-channel-boards-header.title": "Boards",
"share-board.publish": "Publicar", "share-board.publish": "Publicar",
"share-board.share": "Compartilhar", "share-board.share": "Compartilhar",

View File

@ -2,14 +2,14 @@
"AppBar.Tooltip": "Переключить связанные доски", "AppBar.Tooltip": "Переключить связанные доски",
"Attachment.Attachment-title": "Вложение", "Attachment.Attachment-title": "Вложение",
"AttachmentBlock.DeleteAction": "Удалить", "AttachmentBlock.DeleteAction": "Удалить",
"AttachmentBlock.addElement": "добавить", "AttachmentBlock.addElement": "добавить {type}",
"AttachmentBlock.delete": "Вложение успешно удалено.", "AttachmentBlock.delete": "Вложение удалено.",
"AttachmentBlock.failed": "Не удалось загрузить файл. Достигнут предел размера вложения.", "AttachmentBlock.failed": "Не удалось загрузить файл, так как превышена квота на размер файла.",
"AttachmentBlock.upload": "Загрузка вложения.", "AttachmentBlock.upload": "Загрузка вложения.",
"AttachmentBlock.uploadSuccess": "Вложение успешно загружено.", "AttachmentBlock.uploadSuccess": "Вложение загружено.",
"AttachmentElement.delete-confirmation-dialog-button-text": "Удалить", "AttachmentElement.delete-confirmation-dialog-button-text": "Удалить",
"AttachmentElement.download": "Скачать", "AttachmentElement.download": "Скачать",
"AttachmentElement.upload-percentage": "Загрузка", "AttachmentElement.upload-percentage": "Загрузка...({uploadPercent}%)",
"BoardComponent.add-a-group": "+ Добавить группу", "BoardComponent.add-a-group": "+ Добавить группу",
"BoardComponent.delete": "Удалить", "BoardComponent.delete": "Удалить",
"BoardComponent.hidden-columns": "Скрытые столбцы", "BoardComponent.hidden-columns": "Скрытые столбцы",
@ -31,12 +31,12 @@
"BoardTemplateSelector.delete-template": "Удалить", "BoardTemplateSelector.delete-template": "Удалить",
"BoardTemplateSelector.description": "Добавьте доску на боковую панель, используя любой из шаблонов, описанных ниже, или начните с нуля.", "BoardTemplateSelector.description": "Добавьте доску на боковую панель, используя любой из шаблонов, описанных ниже, или начните с нуля.",
"BoardTemplateSelector.edit-template": "Изменить", "BoardTemplateSelector.edit-template": "Изменить",
"BoardTemplateSelector.plugin.no-content-description": "Добавьте доску на боковую панель, используя любой из указанных ниже шаблонов, или начните с нуля.{lineBreak} Участники \"{teamName}\" будут иметь доступ к созданным здесь доскам.", "BoardTemplateSelector.plugin.no-content-description": "Добавьте доску на боковую панель, используя любой из указанных ниже шаблонов, или начните с нуля.",
"BoardTemplateSelector.plugin.no-content-title": "Создать доску", "BoardTemplateSelector.plugin.no-content-title": "Создать доску",
"BoardTemplateSelector.title": "Создать доску", "BoardTemplateSelector.title": "Создать доску",
"BoardTemplateSelector.use-this-template": "Использовать этот шаблон", "BoardTemplateSelector.use-this-template": "Использовать этот шаблон",
"BoardsSwitcher.Title": "Найти доски", "BoardsSwitcher.Title": "Найти доски",
"BoardsUnfurl.Limited": "Информация скрыта в связи с тем, что карточка находится в архиве", "BoardsUnfurl.Limited": "Информация скрыта, потому что карточка находится в архиве",
"BoardsUnfurl.Remainder": "+{remainder} ещё", "BoardsUnfurl.Remainder": "+{remainder} ещё",
"BoardsUnfurl.Updated": "Обновлено {time}", "BoardsUnfurl.Updated": "Обновлено {time}",
"Calculations.Options.average.displayName": "Среднее", "Calculations.Options.average.displayName": "Среднее",
@ -103,9 +103,9 @@
"CardDetailProperty.property-deleted": "{propertyName} успешно удалено!", "CardDetailProperty.property-deleted": "{propertyName} успешно удалено!",
"CardDetailProperty.property-name-change-subtext": "тип из \"{oldPropType}\" в \"{newPropType}\"", "CardDetailProperty.property-name-change-subtext": "тип из \"{oldPropType}\" в \"{newPropType}\"",
"CardDetial.limited-link": "Узнайте больше о наших планах.", "CardDetial.limited-link": "Узнайте больше о наших планах.",
"CardDialog.delete-confirmation-dialog-attachment": "Подтвердите удаление вложения!", "CardDialog.delete-confirmation-dialog-attachment": "Подтвердите удаление вложения",
"CardDialog.delete-confirmation-dialog-button-text": "Удалить", "CardDialog.delete-confirmation-dialog-button-text": "Удалить",
"CardDialog.delete-confirmation-dialog-heading": "Подтвердите удаление карточки!", "CardDialog.delete-confirmation-dialog-heading": "Подтвердите удаление карточки",
"CardDialog.editing-template": "Вы редактируете шаблон.", "CardDialog.editing-template": "Вы редактируете шаблон.",
"CardDialog.nocard": "Эта карточка не существует или недоступна.", "CardDialog.nocard": "Эта карточка не существует или недоступна.",
"Categories.CreateCategoryDialog.CancelText": "Отмена", "Categories.CreateCategoryDialog.CancelText": "Отмена",
@ -114,10 +114,13 @@
"Categories.CreateCategoryDialog.UpdateText": "Обновить", "Categories.CreateCategoryDialog.UpdateText": "Обновить",
"CenterPanel.Login": "Логин", "CenterPanel.Login": "Логин",
"CenterPanel.Share": "Поделиться", "CenterPanel.Share": "Поделиться",
"ChannelIntro.CreateBoard": "Создать доску",
"CloudMessage.cloud-server": "Получите свой бесплатный облачный сервер.", "CloudMessage.cloud-server": "Получите свой бесплатный облачный сервер.",
"ColorOption.selectColor": "Выберите цвет {color}", "ColorOption.selectColor": "Выберите цвет {color}",
"Comment.delete": "Удалить", "Comment.delete": "Удалить",
"CommentsList.send": "Отправить", "CommentsList.send": "Отправить",
"ConfirmPerson.empty": "Пусто",
"ConfirmPerson.search": "Поиск...",
"ConfirmationDialog.cancel-action": "Отмена", "ConfirmationDialog.cancel-action": "Отмена",
"ConfirmationDialog.confirm-action": "Подтвердить", "ConfirmationDialog.confirm-action": "Подтвердить",
"ContentBlock.Delete": "Удалить", "ContentBlock.Delete": "Удалить",
@ -165,6 +168,7 @@
"FilterByText.placeholder": "фильтровать текст", "FilterByText.placeholder": "фильтровать текст",
"FilterComponent.add-filter": "+ Добавить фильтр", "FilterComponent.add-filter": "+ Добавить фильтр",
"FilterComponent.delete": "Удалить", "FilterComponent.delete": "Удалить",
"FilterValue.empty": "(пусто)",
"FindBoardsDialog.IntroText": "Поиск досок", "FindBoardsDialog.IntroText": "Поиск досок",
"FindBoardsDialog.NoResultsFor": "Нет результатов для \"{searchQuery}\"", "FindBoardsDialog.NoResultsFor": "Нет результатов для \"{searchQuery}\"",
"FindBoardsDialog.NoResultsSubtext": "Проверьте правильность написания или попробуйте другой запрос.", "FindBoardsDialog.NoResultsSubtext": "Проверьте правильность написания или попробуйте другой запрос.",
@ -183,7 +187,7 @@
"OnboardingTour.AddComments.Title": "Добавить комментарии", "OnboardingTour.AddComments.Title": "Добавить комментарии",
"OnboardingTour.AddDescription.Body": "Добавьте описание к своей карточке, чтобы Ваши коллеги по команде знали, о чем эта карточка.", "OnboardingTour.AddDescription.Body": "Добавьте описание к своей карточке, чтобы Ваши коллеги по команде знали, о чем эта карточка.",
"OnboardingTour.AddDescription.Title": "Добавить описание", "OnboardingTour.AddDescription.Title": "Добавить описание",
"OnboardingTour.AddProperties.Body": "Добавляйте различные свойства карточкам, чтобы сделать их более мощными!", "OnboardingTour.AddProperties.Body": "Добавляйте различные свойства карточкам, чтобы сделать их более значительными.",
"OnboardingTour.AddProperties.Title": "Добавить свойства", "OnboardingTour.AddProperties.Title": "Добавить свойства",
"OnboardingTour.AddView.Body": "Перейдите сюда, чтобы создать новый вид для организации доски с использованием различных макетов.", "OnboardingTour.AddView.Body": "Перейдите сюда, чтобы создать новый вид для организации доски с использованием различных макетов.",
"OnboardingTour.AddView.Title": "Добавить новый вид", "OnboardingTour.AddView.Title": "Добавить новый вид",
@ -284,6 +288,7 @@
"TableHeaderMenu.insert-right": "Вставить справа", "TableHeaderMenu.insert-right": "Вставить справа",
"TableHeaderMenu.sort-ascending": "Сортировать по возрастанию", "TableHeaderMenu.sort-ascending": "Сортировать по возрастанию",
"TableHeaderMenu.sort-descending": "Сортировать по убыванию", "TableHeaderMenu.sort-descending": "Сортировать по убыванию",
"TableRow.DuplicateCard": "дублировать карточку",
"TableRow.MoreOption": "Больше действий", "TableRow.MoreOption": "Больше действий",
"TableRow.open": "Открыть", "TableRow.open": "Открыть",
"TopBar.give-feedback": "Дать обратную связь", "TopBar.give-feedback": "Дать обратную связь",
@ -351,10 +356,13 @@
"WelcomePage.Explore.Button": "Исследовать", "WelcomePage.Explore.Button": "Исследовать",
"WelcomePage.Heading": "Добро пожаловать на Доски", "WelcomePage.Heading": "Добро пожаловать на Доски",
"WelcomePage.NoThanks.Text": "Нет спасибо, сам разберусь", "WelcomePage.NoThanks.Text": "Нет спасибо, сам разберусь",
"WelcomePage.StartUsingIt.Text": "Начать пользоваться",
"Workspace.editing-board-template": "Вы редактируете шаблон доски.", "Workspace.editing-board-template": "Вы редактируете шаблон доски.",
"badge.guest": "Гость",
"boardSelector.confirm-link-board": "Привязать доску к каналу", "boardSelector.confirm-link-board": "Привязать доску к каналу",
"boardSelector.confirm-link-board-button": "Да, ссылка доски", "boardSelector.confirm-link-board-button": "Да, ссылка доски",
"boardSelector.confirm-link-board-subtext": "Связывание доски \"{boardName}\" с этим каналом даст всем участникам этого канала доступ на редактирование доски. Вы уверены, что хотите связать это?", "boardSelector.confirm-link-board-subtext": "Связывание доски \"{boardName}\" с каналом даст всем участникам канала доступ на редактирование доски. Вы можете в любое время отвязать доску о канала.",
"boardSelector.confirm-link-board-subtext-with-other-channel": "Привязка \"{boardName}\" с каналом приведет к возможности её редактирования всеми участниками канала (существующими и новыми). Кроме гостей канала.{lineBreak} Эта доска сейчас связана с другим каналом. Он будет отключен, если вы решите изменить привязку.",
"boardSelector.create-a-board": "Создать доску", "boardSelector.create-a-board": "Создать доску",
"boardSelector.link": "Ссылка", "boardSelector.link": "Ссылка",
"boardSelector.search-for-boards": "Поиск досок", "boardSelector.search-for-boards": "Поиск досок",
@ -363,6 +371,8 @@
"calendar.month": "Месяц", "calendar.month": "Месяц",
"calendar.today": "СЕГОДНЯ", "calendar.today": "СЕГОДНЯ",
"calendar.week": "Неделя", "calendar.week": "Неделя",
"centerPanel.undefined": "Отсутствует {propertyName}",
"centerPanel.unknown-user": "Неизвестный пользователь",
"cloudMessage.learn-more": "Учить больше", "cloudMessage.learn-more": "Учить больше",
"createImageBlock.failed": "Не удалось загрузить файл. Достигнут предел размера файла.", "createImageBlock.failed": "Не удалось загрузить файл. Достигнут предел размера файла.",
"default-properties.badges": "Комментарии и описание", "default-properties.badges": "Комментарии и описание",
@ -377,11 +387,15 @@
"error.team-undefined": "Не корректная команда.", "error.team-undefined": "Не корректная команда.",
"error.unknown": "Произошла ошибка.", "error.unknown": "Произошла ошибка.",
"generic.previous": "Предыдущий", "generic.previous": "Предыдущий",
"imagePaste.upload-failed": "Некоторые файлы не загружены. Достигнут предел размера файла", "imagePaste.upload-failed": "Некоторые файлы не загружены из-за превышения квоты на размер файла.",
"limitedCard.title": "Карточки скрыты", "limitedCard.title": "Карточки скрыты",
"login.log-in-button": "Вход в систему", "login.log-in-button": "Вход в систему",
"login.log-in-title": "Вход в систему", "login.log-in-title": "Вход в систему",
"login.register-button": "или создать аккаунт, если у Вас его нет", "login.register-button": "или создать аккаунт, если у Вас его нет",
"new_channel_modal.create_board.empty_board_description": "Создать новую пустую доску",
"new_channel_modal.create_board.empty_board_title": "Пустая доска",
"new_channel_modal.create_board.select_template_placeholder": "Выбрать шаблон",
"new_channel_modal.create_board.title": "Создать доску для этого канала",
"notification-box-card-limit-reached.close-tooltip": "Отложить на 10 дней", "notification-box-card-limit-reached.close-tooltip": "Отложить на 10 дней",
"notification-box-card-limit-reached.contact-link": "уведомить Вашего администратора", "notification-box-card-limit-reached.contact-link": "уведомить Вашего администратора",
"notification-box-card-limit-reached.link": "Перейти на платный тариф", "notification-box-card-limit-reached.link": "Перейти на платный тариф",
@ -389,13 +403,18 @@
"notification-box-cards-hidden.title": "Это действие скрыло другую карточку", "notification-box-cards-hidden.title": "Это действие скрыло другую карточку",
"notification-box.card-limit-reached.not-admin.text": "Чтобы получить доступ к архивным карточкам, Вы можете {contactLink} перейти на платный тариф.", "notification-box.card-limit-reached.not-admin.text": "Чтобы получить доступ к архивным карточкам, Вы можете {contactLink} перейти на платный тариф.",
"notification-box.card-limit-reached.text": "Достигнут лимит карточки, чтобы просмотреть старые карточки, {link}", "notification-box.card-limit-reached.text": "Достигнут лимит карточки, чтобы просмотреть старые карточки, {link}",
"person.add-user-to-board": "Добавить {username} на доску",
"person.add-user-to-board-confirm-button": "Добавить доску",
"person.add-user-to-board-permissions": "Разрешения",
"person.add-user-to-board-question": "Вы хотите добавить {username} на доску?",
"register.login-button": "или войти в систему, если у вас уже есть аккаунт", "register.login-button": "или войти в систему, если у вас уже есть аккаунт",
"register.signup-title": "Зарегистрируйте свой аккаунт", "register.signup-title": "Зарегистрируйте свой аккаунт",
"rhs-board-non-admin-msg": "Вы не являетесь администратором этой доски",
"rhs-boards.add": "Добавить", "rhs-boards.add": "Добавить",
"rhs-boards.last-update-at": "Последнее обновление: {datetime}", "rhs-boards.last-update-at": "Последнее обновление: {datetime}",
"rhs-boards.link-boards-to-channel": "Связать доски с {channelName}", "rhs-boards.link-boards-to-channel": "Связать доски с {channelName}",
"rhs-boards.linked-boards": "Связанные доски", "rhs-boards.linked-boards": "Связанные доски",
"rhs-boards.no-boards-linked-to-channel": "К каналу {channelName} пока не подключены доски.", "rhs-boards.no-boards-linked-to-channel": "К каналу {channelName} пока не подключены доски",
"rhs-boards.no-boards-linked-to-channel-description": "Доски — это инструмент управления проектами, который помогает определять, организовывать, отслеживать и управлять работой между командами, используя знакомое представление доски Канбан.", "rhs-boards.no-boards-linked-to-channel-description": "Доски — это инструмент управления проектами, который помогает определять, организовывать, отслеживать и управлять работой между командами, используя знакомое представление доски Канбан.",
"rhs-boards.unlink-board": "Отвязать доску", "rhs-boards.unlink-board": "Отвязать доску",
"rhs-channel-boards-header.title": "Доски", "rhs-channel-boards-header.title": "Доски",

View File

@ -1,12 +1,12 @@
{ {
"name": "focalboard", "name": "focalboard",
"version": "7.9.0", "version": "7.10.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "focalboard", "name": "focalboard",
"version": "7.9.0", "version": "7.10.0",
"dependencies": { "dependencies": {
"@draft-js-plugins/editor": "^4.1.2", "@draft-js-plugins/editor": "^4.1.2",
"@draft-js-plugins/emoji": "^4.6.0", "@draft-js-plugins/emoji": "^4.6.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "focalboard", "name": "focalboard",
"version": "7.9.0", "version": "7.10.0",
"private": true, "private": true,
"description": "", "description": "",
"scripts": { "scripts": {

View File

@ -10,7 +10,7 @@ exports[`components/sidebar/GlobalHeader header menu should match snapshot 1`] =
/> />
<a <a
class="GlobalHeaderComponent__button help-button" class="GlobalHeaderComponent__button help-button"
href="https://www.focalboard.com/fwlink/doc-boards.html?v=7.9.0" href="https://www.focalboard.com/fwlink/doc-boards.html?v=7.10.0"
rel="noreferrer" rel="noreferrer"
target="_blank" target="_blank"
> >

View File

@ -51,9 +51,9 @@ exports[`components/sidebarSidebar dont show hidden boards 1`] = `
> >
<div <div
class="version" class="version"
title="v7.9.0" title="v7.10.0"
> >
v7.9.0 v7.10.0
</div> </div>
</div> </div>
</div> </div>
@ -252,9 +252,9 @@ exports[`components/sidebarSidebar should assign default category if current boa
> >
<div <div
class="version" class="version"
title="v7.9.0" title="v7.10.0"
> >
v7.9.0 v7.10.0
</div> </div>
</div> </div>
</div> </div>
@ -508,9 +508,9 @@ exports[`components/sidebarSidebar shouldnt do any category assignment is board
> >
<div <div
class="version" class="version"
title="v7.9.0" title="v7.10.0"
> >
v7.9.0 v7.10.0
</div> </div>
</div> </div>
</div> </div>
@ -919,9 +919,9 @@ exports[`components/sidebarSidebar sidebar hidden 1`] = `
> >
<div <div
class="version" class="version"
title="v7.9.0" title="v7.10.0"
> >
v7.9.0 v7.10.0
</div> </div>
</div> </div>
</div> </div>
@ -1213,9 +1213,9 @@ exports[`components/sidebarSidebar some categories hidden 1`] = `
> >
<div <div
class="version" class="version"
title="v7.9.0" title="v7.10.0"
> >
v7.9.0 v7.10.0
</div> </div>
</div> </div>
</div> </div>

View File

@ -203,6 +203,7 @@
width: inherit; width: inherit;
} }
.MultiPerson.octo-propertyvalue,
.Person.octo-propertyvalue, .Person.octo-propertyvalue,
.DateRange.octo-propertyvalue { .DateRange.octo-propertyvalue {
overflow: unset; overflow: unset;

View File

@ -37,8 +37,8 @@ class Constants {
static readonly titleColumnId = '__title' static readonly titleColumnId = '__title'
static readonly badgesColumnId = '__badges' static readonly badgesColumnId = '__badges'
static readonly versionString = '7.9.0' static readonly versionString = '7.10.0'
static readonly versionDisplayString = 'Mar 2023' static readonly versionDisplayString = 'Apr 2023'
static readonly archiveHelpPage = 'https://docs.mattermost.com/boards/migrate-to-boards.html' static readonly archiveHelpPage = 'https://docs.mattermost.com/boards/migrate-to-boards.html'
static readonly imports = [ static readonly imports = [

View File

@ -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. Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.
</div> </div>
<div
class="WelcomePage__content"
>
<img <img
alt="Boards Welcome Image" alt="Boards Welcome Image"
class="WelcomePage__image WelcomePage__image--large" 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" class="WelcomePage__image WelcomePage__image--small"
src="test-file-stub" src="test-file-stub"
/> />
<div
class="WelcomePage__buttons"
>
<button <button
class="Button filled size--large" class="Button filled size--large"
type="button" type="button"
@ -47,6 +53,8 @@ exports[`pages/welcome Welcome Page shows Explore Page 1`] = `
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
`; `;
exports[`pages/welcome Welcome Page shows Explore Page with subpath 1`] = ` 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. Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.
</div> </div>
<div
class="WelcomePage__content"
>
<img <img
alt="Boards Welcome Image" alt="Boards Welcome Image"
class="WelcomePage__image WelcomePage__image--large" 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" class="WelcomePage__image WelcomePage__image--small"
src="test-file-stub" src="test-file-stub"
/> />
<div
class="WelcomePage__buttons"
>
<button <button
class="Button filled size--large" class="Button filled size--large"
type="button" type="button"
@ -96,4 +110,6 @@ exports[`pages/welcome Welcome Page shows Explore Page with subpath 1`] = `
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
`; `;

View File

@ -10,6 +10,26 @@
@media (max-height: 768px) { @media (max-height: 768px) {
justify-content: flex-start; justify-content: flex-start;
height: auto; height: auto;
padding-top: 40px;
}
.WelcomePage__content {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
@media (max-height: 800px) {
flex-direction: column-reverse;
margin-top: 16px;
}
}
.WelcomePage__buttons {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
} }
> div { > div {
@ -34,7 +54,6 @@
} }
.skip { .skip {
margin-top: 12px;
color: rgba(var(--link-color-rgb), 1); color: rgba(var(--link-color-rgb), 1);
cursor: pointer; cursor: pointer;

View File

@ -127,6 +127,7 @@ const WelcomePage = () => {
/> />
</div> </div>
<div className='WelcomePage__content'>
{/* This image will be rendered on large screens over 2000px */} {/* This image will be rendered on large screens over 2000px */}
<img <img
src={Utils.buildURL(BoardWelcomePNG, true)} src={Utils.buildURL(BoardWelcomePNG, true)}
@ -141,6 +142,7 @@ const WelcomePage = () => {
alt='Boards Welcome Image' alt='Boards Welcome Image'
/> />
<div className='WelcomePage__buttons'>
{me?.is_guest !== true && {me?.is_guest !== true &&
<Button <Button
onClick={startTour} onClick={startTour}
@ -182,6 +184,8 @@ const WelcomePage = () => {
</Button>} </Button>}
</div> </div>
</div> </div>
</div>
</div>
) )
} }

View File

@ -34,6 +34,23 @@ exports[`properties/dateRange handle clear 1`] = `
</div> </div>
`; `;
exports[`properties/dateRange returns component with new date after prop change 1`] = `
<div>
<div
class="DateRange octo-propertyvalue"
>
<button
class="Button"
type="button"
>
<span>
June 15
</span>
</button>
</div>
</div>
`;
exports[`properties/dateRange returns default correctly 1`] = ` exports[`properties/dateRange returns default correctly 1`] = `
<div> <div>
<div <div

View File

@ -315,4 +315,36 @@ describe('properties/dateRange', () => {
expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify({from: today})) expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify({from: today}))
}) })
test('returns component with new date after prop change', () => {
const component = wrapIntl(
<DateProp
property={new DateProperty()}
propertyValue=''
showEmptyPlaceholder={false}
readOnly={false}
board={{...board}}
card={{...card}}
propertyTemplate={propertyTemplate}
/>,
)
const {container, rerender} = render(component)
rerender(
wrapIntl(
<DateProp
property={new DateProperty()}
propertyValue={'{"from": ' + June15.getTime().toString() + '}'}
showEmptyPlaceholder={false}
readOnly={false}
board={{...board}}
card={{...card}}
propertyTemplate={propertyTemplate}
/>,
),
)
expect(container).toMatchSnapshot()
})
}) })

View File

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useMemo, useState, useCallback} from 'react' import React, {useMemo, useState, useCallback, useEffect} from 'react'
import {useIntl} from 'react-intl' import {useIntl} from 'react-intl'
import {DateUtils} from 'react-day-picker' import {DateUtils} from 'react-day-picker'
import MomentLocaleUtils from 'react-day-picker/moment' import MomentLocaleUtils from 'react-day-picker/moment'
@ -58,6 +58,12 @@ function DateRange(props: PropertyProps): JSX.Element {
const [value, setValue] = useState(propertyValue) const [value, setValue] = useState(propertyValue)
const intl = useIntl() const intl = useIntl()
useEffect(() => {
if (value !== propertyValue) {
setValue(propertyValue)
}
}, [propertyValue, setValue])
const onChange = useCallback((newValue) => { const onChange = useCallback((newValue) => {
if (value !== newValue) { if (value !== newValue) {
setValue(newValue) setValue(newValue)

View File

@ -50,6 +50,7 @@ export const TelemetryActions = {
LimitCardLimitReached: 'limit_cardLimitReached', LimitCardLimitReached: 'limit_cardLimitReached',
LimitCardLimitLinkOpen: 'limit_cardLimitLinkOpen', LimitCardLimitLinkOpen: 'limit_cardLimitLinkOpen',
VersionMoreInfo: 'version_more_info', VersionMoreInfo: 'version_more_info',
ClickChannelsRHSBoard: 'click_board_in_channels_RHS',
} }
interface IEventProps { interface IEventProps {