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",
"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": {

View File

@ -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": {

View File

@ -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')
}

View File

@ -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)

View File

@ -123,37 +123,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.

View File

@ -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)

View File

@ -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)
})
}

View File

@ -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)
}

View File

@ -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",

View File

@ -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 {

View File

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

View File

@ -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"))
}

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.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"
}

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.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",

View File

@ -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": "Доски",

View File

@ -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",

View File

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

View File

@ -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"
>

View File

@ -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>

View File

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

View File

@ -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 = [

View File

@ -18,32 +18,40 @@ exports[`pages/welcome Welcome Page shows Explore Page 1`] = `
>
Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.
</div>
<img
alt="Boards Welcome Image"
class="WelcomePage__image WelcomePage__image--large"
src="test-file-stub"
/>
<img
alt="Boards Welcome Image"
class="WelcomePage__image WelcomePage__image--small"
src="test-file-stub"
/>
<button
class="Button filled size--large"
type="button"
<div
class="WelcomePage__content"
>
<span>
Take a tour
</span>
<i
class="CompassIcon icon-chevron-right Icon Icon--right"
<img
alt="Boards Welcome Image"
class="WelcomePage__image WelcomePage__image--large"
src="test-file-stub"
/>
</button>
<a
class="skip"
>
No thanks, I'll figure it out myself
</a>
<img
alt="Boards Welcome Image"
class="WelcomePage__image WelcomePage__image--small"
src="test-file-stub"
/>
<div
class="WelcomePage__buttons"
>
<button
class="Button filled size--large"
type="button"
>
<span>
Take a tour
</span>
<i
class="CompassIcon icon-chevron-right Icon Icon--right"
/>
</button>
<a
class="skip"
>
No thanks, I'll figure it out myself
</a>
</div>
</div>
</div>
</div>
</div>
@ -67,32 +75,40 @@ exports[`pages/welcome Welcome Page shows Explore Page with subpath 1`] = `
>
Boards is a project management tool that helps define, organize, track, and manage work across teams using a familiar Kanban board view.
</div>
<img
alt="Boards Welcome Image"
class="WelcomePage__image WelcomePage__image--large"
src="test-file-stub"
/>
<img
alt="Boards Welcome Image"
class="WelcomePage__image WelcomePage__image--small"
src="test-file-stub"
/>
<button
class="Button filled size--large"
type="button"
<div
class="WelcomePage__content"
>
<span>
Take a tour
</span>
<i
class="CompassIcon icon-chevron-right Icon Icon--right"
<img
alt="Boards Welcome Image"
class="WelcomePage__image WelcomePage__image--large"
src="test-file-stub"
/>
</button>
<a
class="skip"
>
No thanks, I'll figure it out myself
</a>
<img
alt="Boards Welcome Image"
class="WelcomePage__image WelcomePage__image--small"
src="test-file-stub"
/>
<div
class="WelcomePage__buttons"
>
<button
class="Button filled size--large"
type="button"
>
<span>
Take a tour
</span>
<i
class="CompassIcon icon-chevron-right Icon Icon--right"
/>
</button>
<a
class="skip"
>
No thanks, I'll figure it out myself
</a>
</div>
</div>
</div>
</div>
</div>

View File

@ -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;

View File

@ -127,59 +127,63 @@ const WelcomePage = () => {
/>
</div>
{/* This image will be rendered on large screens over 2000px */}
<img
src={Utils.buildURL(BoardWelcomePNG, true)}
className='WelcomePage__image WelcomePage__image--large'
alt='Boards Welcome Image'
/>
<div className='WelcomePage__content'>
{/* This image will be rendered on large screens over 2000px */}
<img
src={Utils.buildURL(BoardWelcomePNG, true)}
className='WelcomePage__image WelcomePage__image--large'
alt='Boards Welcome Image'
/>
{/* This image will be rendered on small screens below 2000px */}
<img
src={Utils.buildURL(BoardWelcomeSmallPNG, true)}
className='WelcomePage__image WelcomePage__image--small'
alt='Boards Welcome Image'
/>
{/* This image will be rendered on small screens below 2000px */}
<img
src={Utils.buildURL(BoardWelcomeSmallPNG, true)}
className='WelcomePage__image WelcomePage__image--small'
alt='Boards Welcome Image'
/>
{me?.is_guest !== true &&
<Button
onClick={startTour}
filled={true}
size='large'
icon={
<CompassIcon
icon='chevron-right'
className='Icon Icon--right'
/>}
rightIcon={true}
>
<FormattedMessage
id='WelcomePage.Explore.Button'
defaultMessage='Take a tour'
/>
</Button>}
<div className='WelcomePage__buttons'>
{me?.is_guest !== true &&
<Button
onClick={startTour}
filled={true}
size='large'
icon={
<CompassIcon
icon='chevron-right'
className='Icon Icon--right'
/>}
rightIcon={true}
>
<FormattedMessage
id='WelcomePage.Explore.Button'
defaultMessage='Take a tour'
/>
</Button>}
{me?.is_guest !== true &&
<a
className='skip'
onClick={skipTour}
>
<FormattedMessage
id='WelcomePage.NoThanks.Text'
defaultMessage="No thanks, I'll figure it out myself"
/>
</a>}
{me?.is_guest === true &&
<Button
onClick={skipTour}
filled={true}
size='large'
>
<FormattedMessage
id='WelcomePage.StartUsingIt.Text'
defaultMessage='Start using it'
/>
</Button>}
{me?.is_guest !== true &&
<a
className='skip'
onClick={skipTour}
>
<FormattedMessage
id='WelcomePage.NoThanks.Text'
defaultMessage="No thanks, I'll figure it out myself"
/>
</a>}
{me?.is_guest === true &&
<Button
onClick={skipTour}
filled={true}
size='large'
>
<FormattedMessage
id='WelcomePage.StartUsingIt.Text'
defaultMessage='Start using it'
/>
</Button>}
</div>
</div>
</div>
</div>
)

View File

@ -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

View File

@ -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()
})
})

View File

@ -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)

View File

@ -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 {