You've already forked focalboard
							
							
				mirror of
				https://github.com/mattermost/focalboard.git
				synced 2025-10-31 00:17:42 +02:00 
			
		
		
		
	Merge branch 'main' into compliance-history-export
This commit is contained in:
		| @@ -49,6 +49,11 @@ func (a *serviceAPIAdapter) GetDirectChannel(userID1, userID2 string) (*mm_model | ||||
| 	return channel, normalizeAppErr(appErr) | ||||
| } | ||||
|  | ||||
| func (a *serviceAPIAdapter) GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) { | ||||
| 	channel, appErr := a.api.channelService.GetDirectChannelOrCreate(userID1, userID2) | ||||
| 	return channel, normalizeAppErr(appErr) | ||||
| } | ||||
|  | ||||
| func (a *serviceAPIAdapter) GetChannelByID(channelID string) (*mm_model.Channel, error) { | ||||
| 	channel, appErr := a.api.channelService.GetChannelByID(channelID) | ||||
| 	return channel, normalizeAppErr(appErr) | ||||
|   | ||||
| @@ -54,6 +54,12 @@ func (a *pluginAPIAdapter) GetDirectChannel(userID1, userID2 string) (*mm_model. | ||||
| 	return channel, normalizeAppErr(appErr) | ||||
| } | ||||
|  | ||||
| func (a *pluginAPIAdapter) GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) { | ||||
| 	// plugin API's GetDirectChannel will create channel if it does not exist. | ||||
| 	channel, appErr := a.api.GetDirectChannel(userID1, userID2) | ||||
| 	return channel, normalizeAppErr(appErr) | ||||
| } | ||||
|  | ||||
| func (a *pluginAPIAdapter) GetChannelByID(channelID string) (*mm_model.Channel, error) { | ||||
| 	channel, appErr := a.api.GetChannel(channelID) | ||||
| 	return channel, normalizeAppErr(appErr) | ||||
|   | ||||
| @@ -80,6 +80,9 @@ func (b *BoardsApp) OnConfigurationChange() error { | ||||
| 	if mmconfig.PluginSettings.Plugins[PluginName][SharedBoardsName] == true { | ||||
| 		enableShareBoards = true | ||||
| 	} | ||||
| 	if mmconfig.ProductSettings.EnablePublicSharedBoards != nil { | ||||
| 		enableShareBoards = *mmconfig.ProductSettings.EnablePublicSharedBoards | ||||
| 	} | ||||
| 	configuration := &configuration{ | ||||
| 		EnablePublicSharedBoards: enableShareBoards, | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										3
									
								
								mattermost-plugin/server/manifest.go
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								mattermost-plugin/server/manifest.go
									
									
									
										generated
									
									
									
								
							| @@ -45,8 +45,7 @@ const manifestStr = ` | ||||
|         "type": "bool", | ||||
|         "help_text": "This allows board editors to share boards that can be accessed by anyone with the link.", | ||||
|         "placeholder": "", | ||||
|         "default": false, | ||||
|         "hosting": "" | ||||
|         "default": false | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
|   | ||||
| @@ -92,14 +92,25 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.Exp | ||||
| 			return err | ||||
| 		} | ||||
| 		if block.Type == model.TypeImage { | ||||
| 			filename, err := extractImageFilename(block) | ||||
| 			if err != nil { | ||||
| 			filename, err2 := extractImageFilename(block) | ||||
| 			if err2 != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			files = append(files, filename) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	boardMembers, err := a.GetMembersForBoard(board.ID) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, boardMember := range boardMembers { | ||||
| 		if err = a.writeArchiveBoardMemberLine(w, boardMember); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// write the files | ||||
| 	for _, filename := range files { | ||||
| 		if err := a.writeArchiveFile(zw, filename, board.ID, opt); err != nil { | ||||
| @@ -109,6 +120,31 @@ func (a *App) writeArchiveBoard(zw *zip.Writer, board model.Board, opt model.Exp | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // writeArchiveBoardMemberLine writes a single boardMember to the archive. | ||||
| func (a *App) writeArchiveBoardMemberLine(w io.Writer, boardMember *model.BoardMember) error { | ||||
| 	bm, err := json.Marshal(&boardMember) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	line := model.ArchiveLine{ | ||||
| 		Type: "boardMember", | ||||
| 		Data: bm, | ||||
| 	} | ||||
|  | ||||
| 	bm, err = json.Marshal(&line) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	_, err = w.Write(bm) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	_, err = w.Write(newline) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // writeArchiveBlockLine writes a single block to the archive. | ||||
| func (a *App) writeArchiveBlockLine(w io.Writer, block *model.Block) error { | ||||
| 	b, err := json.Marshal(&block) | ||||
|   | ||||
| @@ -137,6 +137,7 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str | ||||
| 	} | ||||
| 	now := utils.GetMillis() | ||||
| 	var boardID string | ||||
| 	var boardMembers []*model.BoardMember | ||||
|  | ||||
| 	lineNum := 1 | ||||
| 	firstLine := true | ||||
| @@ -196,6 +197,12 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str | ||||
| 					block.UpdateAt = now | ||||
| 					block.BoardID = boardID | ||||
| 					boardsAndBlocks.Blocks = append(boardsAndBlocks.Blocks, block) | ||||
| 				case "boardMember": | ||||
| 					var boardMember *model.BoardMember | ||||
| 					if err2 := json.Unmarshal(archiveLine.Data, &boardMember); err2 != nil { | ||||
| 						return "", fmt.Errorf("invalid board Member in archive line %d: %w", lineNum, err2) | ||||
| 					} | ||||
| 					boardMembers = append(boardMembers, boardMember) | ||||
| 				default: | ||||
| 					return "", model.NewErrUnsupportedArchiveLineType(lineNum, archiveLine.Type) | ||||
| 				} | ||||
| @@ -212,6 +219,13 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str | ||||
| 		lineNum++ | ||||
| 	} | ||||
|  | ||||
| 	// loop to remove the people how are not part of the team and system | ||||
| 	for i := len(boardMembers) - 1; i >= 0; i-- { | ||||
| 		if _, err := a.GetUser(boardMembers[i].UserID); err != nil { | ||||
| 			boardMembers = append(boardMembers[:i], boardMembers[i+1:]...) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	a.fixBoardsandBlocks(boardsAndBlocks, opt) | ||||
|  | ||||
| 	var err error | ||||
| @@ -225,16 +239,22 @@ func (a *App) ImportBoardJSONL(r io.Reader, opt model.ImportArchiveOptions) (str | ||||
| 		return "", fmt.Errorf("error inserting archive blocks: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// add user to all the new boards (if not the fake system user). | ||||
| 	if opt.ModifiedBy != model.SystemUserID { | ||||
| 		for _, board := range boardsAndBlocks.Boards { | ||||
| 			boardMember := &model.BoardMember{ | ||||
| 				BoardID:     board.ID, | ||||
| 				UserID:      opt.ModifiedBy, | ||||
| 				SchemeAdmin: true, | ||||
| 	// add users to all the new boards (if not the fake system user). | ||||
| 	for _, board := range boardsAndBlocks.Boards { | ||||
| 		for _, boardMember := range boardMembers { | ||||
| 			bm := &model.BoardMember{ | ||||
| 				BoardID:         board.ID, | ||||
| 				UserID:          boardMember.UserID, | ||||
| 				Roles:           boardMember.Roles, | ||||
| 				MinimumRole:     boardMember.MinimumRole, | ||||
| 				SchemeAdmin:     boardMember.SchemeAdmin, | ||||
| 				SchemeEditor:    boardMember.SchemeEditor, | ||||
| 				SchemeCommenter: boardMember.SchemeCommenter, | ||||
| 				SchemeViewer:    boardMember.SchemeViewer, | ||||
| 				Synthetic:       boardMember.Synthetic, | ||||
| 			} | ||||
| 			if _, err := a.AddMemberToBoard(boardMember); err != nil { | ||||
| 				return "", fmt.Errorf("cannot add member to board: %w", err) | ||||
| 			if _, err2 := a.AddMemberToBoard(bm); err2 != nil { | ||||
| 				return "", fmt.Errorf("cannot add member to board: %w", err2) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -47,8 +47,6 @@ func TestApp_ImportArchive(t *testing.T) { | ||||
|  | ||||
| 		th.Store.EXPECT().CreateBoardsAndBlocks(gomock.AssignableToTypeOf(&model.BoardsAndBlocks{}), "user").Return(babs, nil) | ||||
| 		th.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{boardMember}, nil) | ||||
| 		th.Store.EXPECT().GetBoard(board.ID).Return(board, nil) | ||||
| 		th.Store.EXPECT().GetMemberForBoard(board.ID, "user").Return(boardMember, nil) | ||||
| 		th.Store.EXPECT().GetUserCategoryBoards("user", "test-team") | ||||
| 		th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil) | ||||
| 		th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{ | ||||
| @@ -62,6 +60,64 @@ func TestApp_ImportArchive(t *testing.T) { | ||||
| 		err := th.App.ImportArchive(r, opts) | ||||
| 		require.NoError(t, err, "import archive should not fail") | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("import board archive", func(t *testing.T) { | ||||
| 		r := bytes.NewReader([]byte(boardArchive)) | ||||
| 		opts := model.ImportArchiveOptions{ | ||||
| 			TeamID:     "test-team", | ||||
| 			ModifiedBy: "f1tydgc697fcbp8ampr6881jea", | ||||
| 		} | ||||
|  | ||||
| 		bm1 := &model.BoardMember{ | ||||
| 			BoardID: board.ID, | ||||
| 			UserID:  "f1tydgc697fcbp8ampr6881jea", | ||||
| 		} | ||||
|  | ||||
| 		bm2 := &model.BoardMember{ | ||||
| 			BoardID: board.ID, | ||||
| 			UserID:  "hxxzooc3ff8cubsgtcmpn8733e", | ||||
| 		} | ||||
|  | ||||
| 		bm3 := &model.BoardMember{ | ||||
| 			BoardID: board.ID, | ||||
| 			UserID:  "nto73edn5ir6ifimo5a53y1dwa", | ||||
| 		} | ||||
|  | ||||
| 		user1 := &model.User{ | ||||
| 			ID: "f1tydgc697fcbp8ampr6881jea", | ||||
| 		} | ||||
|  | ||||
| 		user2 := &model.User{ | ||||
| 			ID: "hxxzooc3ff8cubsgtcmpn8733e", | ||||
| 		} | ||||
|  | ||||
| 		user3 := &model.User{ | ||||
| 			ID: "nto73edn5ir6ifimo5a53y1dwa", | ||||
| 		} | ||||
|  | ||||
| 		th.Store.EXPECT().CreateBoardsAndBlocks(gomock.AssignableToTypeOf(&model.BoardsAndBlocks{}), "f1tydgc697fcbp8ampr6881jea").Return(babs, nil) | ||||
| 		th.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{bm1, bm2, bm3}, nil) | ||||
| 		th.Store.EXPECT().GetUserCategoryBoards("f1tydgc697fcbp8ampr6881jea", "test-team") | ||||
| 		th.Store.EXPECT().CreateCategory(utils.Anything).Return(nil) | ||||
| 		th.Store.EXPECT().GetCategory(utils.Anything).Return(&model.Category{ | ||||
| 			ID:   "boards_category_id", | ||||
| 			Name: "Boards", | ||||
| 		}, nil) | ||||
| 		th.Store.EXPECT().GetMembersForUser("f1tydgc697fcbp8ampr6881jea").Return([]*model.BoardMember{}, nil) | ||||
| 		th.Store.EXPECT().GetBoardsForUserAndTeam("f1tydgc697fcbp8ampr6881jea", "test-team", false).Return([]*model.Board{}, nil) | ||||
| 		th.Store.EXPECT().AddUpdateCategoryBoard("f1tydgc697fcbp8ampr6881jea", utils.Anything).Return(nil) | ||||
| 		th.Store.EXPECT().GetBoard(board.ID).AnyTimes().Return(board, nil) | ||||
| 		th.Store.EXPECT().GetMemberForBoard(board.ID, "f1tydgc697fcbp8ampr6881jea").AnyTimes().Return(bm1, nil) | ||||
| 		th.Store.EXPECT().GetMemberForBoard(board.ID, "hxxzooc3ff8cubsgtcmpn8733e").AnyTimes().Return(bm2, nil) | ||||
| 		th.Store.EXPECT().GetMemberForBoard(board.ID, "nto73edn5ir6ifimo5a53y1dwa").AnyTimes().Return(bm3, nil) | ||||
| 		th.Store.EXPECT().GetUserByID("f1tydgc697fcbp8ampr6881jea").AnyTimes().Return(user1, nil) | ||||
| 		th.Store.EXPECT().GetUserByID("hxxzooc3ff8cubsgtcmpn8733e").AnyTimes().Return(user2, nil) | ||||
| 		th.Store.EXPECT().GetUserByID("nto73edn5ir6ifimo5a53y1dwa").AnyTimes().Return(user3, nil) | ||||
|  | ||||
| 		boardID, err := th.App.ImportBoardJSONL(r, opts) | ||||
| 		require.Equal(t, board.ID, boardID, "Board ID should be same") | ||||
| 		require.NoError(t, err, "import archive should not fail") | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| //nolint:lll | ||||
| @@ -78,3 +134,12 @@ const asana = `{"version":1,"date":1614714686842} | ||||
| {"type":"block","data":{"id":"db1dd596-0999-4741-8b05-72ca8e438e31","fields":{"icon":"","properties":{"3bdcbaeb-bc78-4884-8531-a0323b74676a":"deaab476-c690-48df-828f-725b064dc476"},"contentOrder":[]},"createAt":1614714686841,"updateAt":1614714686841,"deleteAt":0,"schema":1,"parentId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","rootId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","modifiedBy":"","type":"card","title":"[EXAMPLE TASK] Approve campaign copy"}} | ||||
| {"type":"block","data":{"id":"16861c05-f31f-46af-8429-80a87b5aa93a","fields":{"icon":"","properties":{"3bdcbaeb-bc78-4884-8531-a0323b74676a":"2138305a-3157-461c-8bbe-f19ebb55846d"},"contentOrder":[]},"createAt":1614714686841,"updateAt":1614714686841,"deleteAt":0,"schema":1,"parentId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","rootId":"d14b9df9-1f31-4732-8a64-92bc7162cd28","modifiedBy":"","type":"card","title":"[EXAMPLE TASK] Send out updated attendee list"}} | ||||
| ` | ||||
|  | ||||
| //nolint:lll | ||||
| const boardArchive = `{"type":"board","data":{"id":"bfoi6yy6pa3yzika53spj7pq9ee","teamId":"wsmqbtwb5jb35jb3mtp85c8a9h","channelId":"","createdBy":"nto73edn5ir6ifimo5a53y1dwa","modifiedBy":"nto73edn5ir6ifimo5a53y1dwa","type":"P","minimumRole":"","title":"Custom","description":"","icon":"","showDescription":false,"isTemplate":false,"templateVersion":0,"properties":{},"cardProperties":[{"id":"aonihehbifijmx56aqzu3cc7w1r","name":"Status","options":[],"type":"select"},{"id":"aohjkzt769rxhtcz1o9xcoce5to","name":"Person","options":[],"type":"person"}],"createAt":1672750481591,"updateAt":1672750481591,"deleteAt":0}} | ||||
| {"type":"block","data":{"id":"ckpc3b1dp3pbw7bqntfryy9jbzo","parentId":"bjaqxtbyqz3bu7pgyddpgpms74a","createdBy":"nto73edn5ir6ifimo5a53y1dwa","modifiedBy":"nto73edn5ir6ifimo5a53y1dwa","schema":1,"type":"card","title":"Test","fields":{"contentOrder":[],"icon":"","isTemplate":false,"properties":{"aohjkzt769rxhtcz1o9xcoce5to":"hxxzooc3ff8cubsgtcmpn8733e"}},"createAt":1672750481612,"updateAt":1672845003530,"deleteAt":0,"boardId":"bfoi6yy6pa3yzika53spj7pq9ee"}} | ||||
| {"type":"block","data":{"id":"v7tdajwpm47r3u8duedk89bhxar","parentId":"bpypang3a3errqstj1agx9kuqay","createdBy":"nto73edn5ir6ifimo5a53y1dwa","modifiedBy":"nto73edn5ir6ifimo5a53y1dwa","schema":1,"type":"view","title":"Board view","fields":{"cardOrder":["crsyw7tbr3pnjznok6ppngmmyya","c5titiemp4pgaxbs4jksgybbj4y"],"collapsedOptionIds":[],"columnCalculations":{},"columnWidths":{},"defaultTemplateId":"","filter":{"filters":[],"operation":"and"},"hiddenOptionIds":[],"kanbanCalculations":{},"sortOptions":[],"viewType":"board","visibleOptionIds":[],"visiblePropertyIds":["aohjkzt769rxhtcz1o9xcoce5to"]},"createAt":1672750481626,"updateAt":1672750481626,"deleteAt":0,"boardId":"bfoi6yy6pa3yzika53spj7pq9ee"}} | ||||
| {"type":"boardMember","data":{"boardId":"bfoi6yy6pa3yzika53spj7pq9ee","userId":"f1tydgc697fcbp8ampr6881jea","roles":"","minimumRole":"","schemeAdmin":false,"schemeEditor":false,"schemeCommenter":false,"schemeViewer":true,"synthetic":false}} | ||||
| {"type":"boardMember","data":{"boardId":"bfoi6yy6pa3yzika53spj7pq9ee","userId":"hxxzooc3ff8cubsgtcmpn8733e","roles":"","minimumRole":"","schemeAdmin":false,"schemeEditor":false,"schemeCommenter":false,"schemeViewer":true,"synthetic":false}} | ||||
| {"type":"boardMember","data":{"boardId":"bfoi6yy6pa3yzika53spj7pq9ee","userId":"nto73edn5ir6ifimo5a53y1dwa","roles":"","minimumRole":"","schemeAdmin":true,"schemeEditor":false,"schemeCommenter":false,"schemeViewer":false,"synthetic":false}} | ||||
| ` | ||||
|   | ||||
| @@ -199,6 +199,21 @@ func (mr *MockServicesAPIMockRecorder) GetDirectChannel(arg0, arg1 interface{}) | ||||
| 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDirectChannel", reflect.TypeOf((*MockServicesAPI)(nil).GetDirectChannel), arg0, arg1) | ||||
| } | ||||
|  | ||||
| // GetDirectChannelOrCreate mocks base method. | ||||
| func (m *MockServicesAPI) GetDirectChannelOrCreate(arg0, arg1 string) (*model.Channel, error) { | ||||
| 	m.ctrl.T.Helper() | ||||
| 	ret := m.ctrl.Call(m, "GetDirectChannelOrCreate", arg0, arg1) | ||||
| 	ret0, _ := ret[0].(*model.Channel) | ||||
| 	ret1, _ := ret[1].(error) | ||||
| 	return ret0, ret1 | ||||
| } | ||||
|  | ||||
| // GetDirectChannelOrCreate indicates an expected call of GetDirectChannelOrCreate. | ||||
| func (mr *MockServicesAPIMockRecorder) GetDirectChannelOrCreate(arg0, arg1 interface{}) *gomock.Call { | ||||
| 	mr.mock.ctrl.T.Helper() | ||||
| 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDirectChannelOrCreate", reflect.TypeOf((*MockServicesAPI)(nil).GetDirectChannelOrCreate), arg0, arg1) | ||||
| } | ||||
|  | ||||
| // GetFileInfo mocks base method. | ||||
| func (m *MockServicesAPI) GetFileInfo(arg0 string) (*model.FileInfo, error) { | ||||
| 	m.ctrl.T.Helper() | ||||
|   | ||||
| @@ -30,6 +30,7 @@ var FocalboardBot = &mm_model.Bot{ | ||||
| type ServicesAPI interface { | ||||
| 	// Channels service | ||||
| 	GetDirectChannel(userID1, userID2 string) (*mm_model.Channel, error) | ||||
| 	GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) | ||||
| 	GetChannelByID(channelID string) (*mm_model.Channel, error) | ||||
| 	GetChannelMember(channelID string, userID string) (*mm_model.ChannelMember, error) | ||||
| 	GetChannelsForTeamForUser(teamID string, userID string, includeDeleted bool) (mm_model.ChannelList, error) | ||||
|   | ||||
| @@ -8,9 +8,9 @@ import ( | ||||
| ) | ||||
|  | ||||
| type servicesAPI interface { | ||||
| 	// GetDirectChannel gets a direct message channel. | ||||
| 	// If the channel does not exist it will create it. | ||||
| 	GetDirectChannel(userID1, userID2 string) (*mm_model.Channel, error) | ||||
| 	// GetDirectChannelOrCreate gets a direct message channel, | ||||
| 	// or creates one if it does not already exist | ||||
| 	GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) | ||||
|  | ||||
| 	// CreatePost creates a post. | ||||
| 	CreatePost(post *mm_model.Post) (*mm_model.Post, error) | ||||
|   | ||||
| @@ -53,7 +53,7 @@ func (pd *PluginDelivery) getDirectChannelID(teamID string, subscriberID string, | ||||
| 			return "", fmt.Errorf("cannot find user: %w", err) | ||||
| 		} | ||||
| 		channel, err := pd.getDirectChannel(teamID, user.Id, botID) | ||||
| 		if err != nil { | ||||
| 		if err != nil || channel == nil { | ||||
| 			return "", fmt.Errorf("cannot get direct channel: %w", err) | ||||
| 		} | ||||
| 		return channel.Id, nil | ||||
| @@ -70,5 +70,5 @@ func (pd *PluginDelivery) getDirectChannel(teamID string, userID string, botID s | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("cannot add bot to team %s: %w", teamID, err) | ||||
| 	} | ||||
| 	return pd.api.GetDirectChannel(userID, botID) | ||||
| 	return pd.api.GetDirectChannelOrCreate(userID, botID) | ||||
| } | ||||
|   | ||||
| @@ -101,6 +101,10 @@ func (m servicesAPIMock) GetDirectChannel(userID1, userID2 string) (*mm_model.Ch | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| func (m servicesAPIMock) GetDirectChannelOrCreate(userID1, userID2 string) (*mm_model.Channel, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| func (m servicesAPIMock) CreatePost(post *mm_model.Post) (*mm_model.Post, error) { | ||||
| 	return post, nil | ||||
| } | ||||
|   | ||||
| @@ -28,7 +28,7 @@ describe('Create and delete board / card', () => { | ||||
|         cy.contains('Project Tasks').should('exist') | ||||
|  | ||||
|         // Create empty board | ||||
|         cy.contains('Create empty board').should('exist').click({force: true}) | ||||
|         cy.contains('Create an empty board').should('exist').click({force: true}) | ||||
|         cy.get('.BoardComponent').should('exist') | ||||
|         cy.get('.Editable.title').invoke('attr', 'placeholder').should('contain', 'Untitled board') | ||||
|  | ||||
|   | ||||
| @@ -17,5 +17,5 @@ Cypress.Commands.add('uiCreateEmptyBoard', () => { | ||||
|     cy.log('Create new empty board') | ||||
|  | ||||
|     cy.contains('+ Add board').should('be.visible').click().wait(500) | ||||
|     return cy.contains('Create empty board').click({force: true}).wait(1000) | ||||
|     return cy.contains('Create an empty board').click({force: true}).wait(1000) | ||||
| }) | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| { | ||||
|   "AppBar.Tooltip": "Toggle Linked Boards", | ||||
|   "AppBar.Tooltip": "Toggle linked boards", | ||||
|   "Attachment.Attachment-title": "Attachment", | ||||
|   "AttachmentBlock.DeleteAction": "delete", | ||||
|   "AttachmentBlock.addElement": "add {type}", | ||||
|   "AttachmentBlock.delete": "Attachment Deleted Successfully.", | ||||
|   "AttachmentBlock.failed": "Unable to upload the file. Attachment size limit reached.", | ||||
|   "AttachmentBlock.delete": "Attachment deleted.", | ||||
|   "AttachmentBlock.failed": "This file couldn't be uploaded as the file size limit has been reached.", | ||||
|   "AttachmentBlock.upload": "Attachment uploading.", | ||||
|   "AttachmentBlock.uploadSuccess": "Attachment uploaded successfull.", | ||||
|   "AttachmentBlock.uploadSuccess": "Attachment uploaded.", | ||||
|   "AttachmentElement.delete-confirmation-dialog-button-text": "Delete", | ||||
|   "AttachmentElement.download": "Download", | ||||
|   "AttachmentElement.upload-percentage": "Uploading...({uploadPercent}%)", | ||||
| @@ -16,7 +16,7 @@ | ||||
|   "BoardComponent.hide": "Hide", | ||||
|   "BoardComponent.new": "+ New", | ||||
|   "BoardComponent.no-property": "No {property}", | ||||
|   "BoardComponent.no-property-title": "Items with an empty {property} property will go here. This column cannot be removed.", | ||||
|   "BoardComponent.no-property-title": "Items with an empty {property} property will go here. This column can't be removed.", | ||||
|   "BoardComponent.show": "Show", | ||||
|   "BoardMember.schemeAdmin": "Admin", | ||||
|   "BoardMember.schemeCommenter": "Commenter", | ||||
| @@ -27,7 +27,7 @@ | ||||
|   "BoardPage.newVersion": "A new version of Boards is available, click here to reload.", | ||||
|   "BoardPage.syncFailed": "Board may be deleted or access revoked.", | ||||
|   "BoardTemplateSelector.add-template": "Create new template", | ||||
|   "BoardTemplateSelector.create-empty-board": "Create empty board", | ||||
|   "BoardTemplateSelector.create-empty-board": "Create an empty board", | ||||
|   "BoardTemplateSelector.delete-template": "Delete", | ||||
|   "BoardTemplateSelector.description": "Add a board to the sidebar using any of the templates defined below or start from scratch.", | ||||
|   "BoardTemplateSelector.edit-template": "Edit", | ||||
| @@ -35,7 +35,7 @@ | ||||
|   "BoardTemplateSelector.plugin.no-content-title": "Create a board", | ||||
|   "BoardTemplateSelector.title": "Create a board", | ||||
|   "BoardTemplateSelector.use-this-template": "Use this template", | ||||
|   "BoardsSwitcher.Title": "Find Boards", | ||||
|   "BoardsSwitcher.Title": "Find boards", | ||||
|   "BoardsUnfurl.Limited": "Additional details are hidden due to the card being archived", | ||||
|   "BoardsUnfurl.Remainder": "+{remainder} more", | ||||
|   "BoardsUnfurl.Updated": "Updated {time}", | ||||
| @@ -88,7 +88,7 @@ | ||||
|   "CardDetail.add-icon": "Add icon", | ||||
|   "CardDetail.add-property": "+ Add a property", | ||||
|   "CardDetail.addCardText": "add card text", | ||||
|   "CardDetail.limited-body": "Upgrade to our Professional or Enterprise plan to view archived cards, have unlimited views per boards, unlimited cards and more.", | ||||
|   "CardDetail.limited-body": "Upgrade to our Professional or Enterprise plan.", | ||||
|   "CardDetail.limited-button": "Upgrade", | ||||
|   "CardDetail.limited-title": "This card is hidden", | ||||
|   "CardDetail.moveContent": "Move card content", | ||||
| @@ -103,9 +103,9 @@ | ||||
|   "CardDetailProperty.property-deleted": "Deleted {propertyName} successfully!", | ||||
|   "CardDetailProperty.property-name-change-subtext": "type from \"{oldPropType}\" to \"{newPropType}\"", | ||||
|   "CardDetial.limited-link": "Learn more about our plans.", | ||||
|   "CardDialog.delete-confirmation-dialog-attachment": "Confirm Attachment delete!", | ||||
|   "CardDialog.delete-confirmation-dialog-attachment": "Confirm attachment delete", | ||||
|   "CardDialog.delete-confirmation-dialog-button-text": "Delete", | ||||
|   "CardDialog.delete-confirmation-dialog-heading": "Confirm card delete!", | ||||
|   "CardDialog.delete-confirmation-dialog-heading": "Confirm card delete", | ||||
|   "CardDialog.editing-template": "You're editing a template.", | ||||
|   "CardDialog.nocard": "This card doesn't exist or is inaccessible.", | ||||
|   "Categories.CreateCategoryDialog.CancelText": "Cancel", | ||||
| @@ -187,7 +187,7 @@ | ||||
|   "OnboardingTour.AddComments.Title": "Add comments", | ||||
|   "OnboardingTour.AddDescription.Body": "Add a description to your card so your teammates know what the card is about.", | ||||
|   "OnboardingTour.AddDescription.Title": "Add description", | ||||
|   "OnboardingTour.AddProperties.Body": "Add various properties to cards to make them more powerful!", | ||||
|   "OnboardingTour.AddProperties.Body": "Add various properties to cards to make them more powerful.", | ||||
|   "OnboardingTour.AddProperties.Title": "Add properties", | ||||
|   "OnboardingTour.AddView.Body": "Go here to create a new view to organise your board using different layouts.", | ||||
|   "OnboardingTour.AddView.Title": "Add a new view", | ||||
| @@ -237,11 +237,11 @@ | ||||
|   "ShareBoard.copyLink": "Copy link", | ||||
|   "ShareBoard.regenerate": "Regenerate token", | ||||
|   "ShareBoard.searchPlaceholder": "Search for people and channels", | ||||
|   "ShareBoard.teamPermissionsText": "Everyone at {teamName} Team", | ||||
|   "ShareBoard.teamPermissionsText": "Everyone at {teamName} team", | ||||
|   "ShareBoard.tokenRegenrated": "Token regenerated", | ||||
|   "ShareBoard.userPermissionsRemoveMemberText": "Remove member", | ||||
|   "ShareBoard.userPermissionsYouText": "(You)", | ||||
|   "ShareTemplate.Title": "Share Template", | ||||
|   "ShareTemplate.Title": "Share template", | ||||
|   "ShareTemplate.searchPlaceholder": "Search for people", | ||||
|   "Sidebar.about": "About Focalboard", | ||||
|   "Sidebar.add-board": "+ Add board", | ||||
| @@ -277,8 +277,8 @@ | ||||
|   "SidebarTour.SidebarCategories.Body": "All your boards are now organized under your new sidebar. No more switching between workspaces. One-time custom categories based on your prior workspaces may have automatically been created for you as part of your v7.2 upgrade. These can be removed or edited to your preference.", | ||||
|   "SidebarTour.SidebarCategories.Link": "Learn more", | ||||
|   "SidebarTour.SidebarCategories.Title": "Sidebar categories", | ||||
|   "SiteStats.total_boards": "Total Boards", | ||||
|   "SiteStats.total_cards": "Total Cards", | ||||
|   "SiteStats.total_boards": "Total boards", | ||||
|   "SiteStats.total_cards": "Total cards", | ||||
|   "TableComponent.add-icon": "Add icon", | ||||
|   "TableComponent.name": "Name", | ||||
|   "TableComponent.plus-new": "+ New", | ||||
| @@ -342,9 +342,9 @@ | ||||
|   "ViewLimitDialog.Heading": "Views per board limit reached", | ||||
|   "ViewLimitDialog.PrimaryButton.Title.Admin": "Upgrade", | ||||
|   "ViewLimitDialog.PrimaryButton.Title.RegularUser": "Notify Admin", | ||||
|   "ViewLimitDialog.Subtext.Admin": "Upgrade to our Professional or Enterprise plan to have unlimited views per boards, unlimited cards, and more.", | ||||
|   "ViewLimitDialog.Subtext.Admin": "Upgrade to our Professional or Enterprise plan.", | ||||
|   "ViewLimitDialog.Subtext.Admin.PricingPageLink": "Learn more about our plans.", | ||||
|   "ViewLimitDialog.Subtext.RegularUser": "Notify your Admin to upgrade to our Professional or Enterprise plan to have unlimited views per boards, unlimited cards, and more.", | ||||
|   "ViewLimitDialog.Subtext.RegularUser": "Notify your Admin to upgrade to our Professional or Enterprise plan.", | ||||
|   "ViewLimitDialog.UpgradeImg.AltText": "upgrade image", | ||||
|   "ViewLimitDialog.notifyAdmin.Success": "Your admin has been notified", | ||||
|   "ViewTitle.hide-description": "hide description", | ||||
| @@ -375,10 +375,10 @@ | ||||
|   "centerPanel.undefined": "No {propertyName}", | ||||
|   "centerPanel.unknown-user": "Unknown user", | ||||
|   "cloudMessage.learn-more": "Learn more", | ||||
|   "createImageBlock.failed": "Unable to upload the file. File size limit reached.", | ||||
|   "createImageBlock.failed": "This file couldn't be uploaded as the file size limit has been reached.", | ||||
|   "default-properties.badges": "Comments and description", | ||||
|   "default-properties.title": "Title", | ||||
|   "error.back-to-home": "Back to Home", | ||||
|   "error.back-to-home": "Back to home", | ||||
|   "error.back-to-team": "Back to team", | ||||
|   "error.board-not-found": "Board not found.", | ||||
|   "error.go-login": "Log in", | ||||
| @@ -390,7 +390,7 @@ | ||||
|   "generic.previous": "Previous", | ||||
|   "guest-no-board.subtitle": "You don't have access to any board in this team yet, please wait until somebody adds you to any board.", | ||||
|   "guest-no-board.title": "No boards yet", | ||||
|   "imagePaste.upload-failed": "Some files not uploaded. File size limit reached", | ||||
|   "imagePaste.upload-failed": "Some files weren't uploaded because the file size limit has been reached.", | ||||
|   "limitedCard.title": "Cards hidden", | ||||
|   "login.log-in-button": "Log in", | ||||
|   "login.log-in-title": "Log in", | ||||
| @@ -406,15 +406,15 @@ | ||||
|   "person.add-user-to-board-confirm-button": "Add to board", | ||||
|   "person.add-user-to-board-permissions": "Permissions", | ||||
|   "person.add-user-to-board-question": "Do you want to add {username} to the board?", | ||||
|   "person.add-user-to-board-warning": "{username} is not a member of the board, and will not receive any notifications about it.", | ||||
|   "person.add-user-to-board-warning": "{username} isn't a member of the board, and won't receive any notifications about it.", | ||||
|   "register.login-button": "or log in if you already have an account", | ||||
|   "register.signup-title": "Sign up for your account", | ||||
|   "rhs-board-non-admin-msg": "You are not an admin of the board", | ||||
|   "rhs-board-non-admin-msg": "You're not an admin of the board", | ||||
|   "rhs-boards.add": "Add", | ||||
|   "rhs-boards.dm": "DM", | ||||
|   "rhs-boards.gm": "GM", | ||||
|   "rhs-boards.header.dm": "this Direct Message", | ||||
|   "rhs-boards.header.gm": "this Group Message", | ||||
|   "rhs-boards.header.dm": "this direct message", | ||||
|   "rhs-boards.header.gm": "this group message", | ||||
|   "rhs-boards.last-update-at": "Last update at: {datetime}", | ||||
|   "rhs-boards.link-boards-to-channel": "Link boards to {channelName}", | ||||
|   "rhs-boards.linked-boards": "Linked boards", | ||||
| @@ -445,4 +445,4 @@ | ||||
|   "tutorial_tip.ok": "Next", | ||||
|   "tutorial_tip.out": "Opt out of these tips.", | ||||
|   "tutorial_tip.seen": "Seen this before?" | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -165,7 +165,7 @@ | ||||
|   "FilterByText.placeholder": "過濾文字", | ||||
|   "FilterComponent.add-filter": "+ 增加過濾條件", | ||||
|   "FilterComponent.delete": "刪除", | ||||
|   "FindBoardsDialog.IntroText": "查詢面板", | ||||
|   "FindBoardsDialog.IntroText": "查詢看板", | ||||
|   "FindBoardsDialog.NoResultsFor": "「{searchQuery}」搜尋未果", | ||||
|   "FindBoardsDialog.NoResultsSubtext": "檢查錯字或嘗試其他搜尋.", | ||||
|   "FindBoardsDialog.SubTitle": "輸入已找到面板.使用 <b>UP/DOWN</b>來瀏覽.<b>ENTER</b>來搜尋, <b>ESC</b> 來取消", | ||||
| @@ -313,8 +313,9 @@ | ||||
|   "View.NewTemplateDefaultTitle": "沒有標題的模板", | ||||
|   "View.NewTemplateTitle": "沒有標題", | ||||
|   "View.Table": "圖表", | ||||
|   "ViewHeader.add-template": "+ 新範本", | ||||
|   "ViewHeader.add-template": "新範本", | ||||
|   "ViewHeader.delete-template": "刪除", | ||||
|   "ViewHeader.display-by": "依據{property}顯示", | ||||
|   "ViewHeader.edit-template": "編輯", | ||||
|   "ViewHeader.empty-card": "清空卡片", | ||||
|   "ViewHeader.export-board-archive": "匯出版面打包檔", | ||||
| @@ -331,11 +332,14 @@ | ||||
|   "ViewHeader.set-default-template": "設為預設", | ||||
|   "ViewHeader.sort": "排序", | ||||
|   "ViewHeader.untitled": "無標題", | ||||
|   "ViewHeader.view-header-menu": "查看標題菜單", | ||||
|   "ViewHeader.view-menu": "查看菜單", | ||||
|   "ViewLimitDialog.Heading": "已達到每個看板觀看限制", | ||||
|   "ViewLimitDialog.PrimaryButton.Title.Admin": "升級", | ||||
|   "ViewLimitDialog.PrimaryButton.Title.RegularUser": "通知管理者", | ||||
|   "ViewLimitDialog.Subtext.Admin": "升級到專業版或企業版,獲得每個看板無限瀏覽、無限卡片,以及更多。", | ||||
|   "ViewLimitDialog.Subtext.Admin.PricingPageLink": "了解更多我們的計畫", | ||||
|   "ViewLimitDialog.Subtext.Admin.PricingPageLink": "了解更多我們的計畫。", | ||||
|   "ViewLimitDialog.Subtext.RegularUser": "通知你的管理員升級到專業版或是企業版,獲得無限使用看板、卡片、更多。", | ||||
|   "ViewLimitDialog.UpgradeImg.AltText": "升級圖片", | ||||
|   "ViewLimitDialog.notifyAdmin.Success": "已經通知管理者", | ||||
|   "ViewTitle.hide-description": "隱藏敘述", | ||||
| @@ -344,14 +348,17 @@ | ||||
|   "ViewTitle.remove-icon": "移除圖示", | ||||
|   "ViewTitle.show-description": "顯示敘述", | ||||
|   "ViewTitle.untitled-board": "未命名版面", | ||||
|   "WelcomePage.Description": "看板是一個專案管理工具,可以使用熟悉的圖表幫助我們定義、組織、追蹤和管理跨團隊工作。", | ||||
|   "WelcomePage.Explore.Button": "探索", | ||||
|   "WelcomePage.Heading": "歡迎來到版面", | ||||
|   "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-with-other-channel": "當你將\"{boardName}\"連接到頻道時,該頻道的所有成員(現有的和新的)都可以編輯。並不包含訪客身分。{lineBreak} 看板目前正連接到另一個頻道。如果选择在這裡連接它,將取消另一個連接。", | ||||
|   "boardSelector.create-a-board": "建立看版", | ||||
|   "boardSelector.link": "連結", | ||||
|   "boardSelector.search-for-boards": "搜尋看板", | ||||
| @@ -369,12 +376,12 @@ | ||||
|   "error.board-not-found": "沒有找到看板.", | ||||
|   "error.go-login": "登入", | ||||
|   "error.invalid-read-only-board": "沒有權限進入此看版.登入後才能訪問.", | ||||
|   "error.not-logged-in": "已被登出,請再次登入使用看板", | ||||
|   "error.not-logged-in": "已被登出,請再次登入使用看板。", | ||||
|   "error.page.title": "很抱歉,發生了些錯誤", | ||||
|   "error.team-undefined": "不是有效的團隊", | ||||
|   "error.team-undefined": "不是有效的團隊。", | ||||
|   "error.unknown": "發生一個錯誤。", | ||||
|   "generic.previous": "上一篇", | ||||
|   "guest-no-board.subtitle": "你尚未有權限進入此看板,請等人把你加入任何看板", | ||||
|   "guest-no-board.subtitle": "你尚未有權限進入此看板,請等人把你加入任何看板。", | ||||
|   "guest-no-board.title": "尚未有看版", | ||||
|   "imagePaste.upload-failed": "有些檔案無法上傳.檔案大小達上限", | ||||
|   "limitedCard.title": "影藏卡片", | ||||
| @@ -386,7 +393,8 @@ | ||||
|   "notification-box-card-limit-reached.link": "升級到付費版", | ||||
|   "notification-box-card-limit-reached.title": "將看板上{cards}卡片隱藏", | ||||
|   "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}", | ||||
|   "person.add-user-to-board": "將{username} 加入看板", | ||||
|   "person.add-user-to-board-confirm-button": "新增看板", | ||||
|   "person.add-user-to-board-permissions": "權限", | ||||
| @@ -396,22 +404,34 @@ | ||||
|   "register.signup-title": "註冊您的帳戶", | ||||
|   "rhs-board-non-admin-msg": "你不是看板的管理者", | ||||
|   "rhs-boards.add": "新增", | ||||
|   "rhs-boards.dm": "私人訊息", | ||||
|   "rhs-boards.gm": "群組訊息", | ||||
|   "rhs-boards.header.dm": "此私人訊息", | ||||
|   "rhs-boards.header.gm": "此群組訊息", | ||||
|   "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-description": "看板是一個專案管理工具,可以使用熟悉的圖表幫助我們定義、組織、追蹤和管理跨團隊工作。", | ||||
|   "rhs-boards.unlink-board": "未連結看版", | ||||
|   "rhs-boards.unlink-board1": "未連結看版", | ||||
|   "rhs-channel-boards-header.title": "板塊", | ||||
|   "share-board.publish": "發布", | ||||
|   "share-board.share": "分享", | ||||
|   "shareBoard.channels-select-group": "頻道", | ||||
|   "shareBoard.confirm-change-team-role.body": "此看板上所有低於\"{role}\"的人都將<b>被提升到{role}</b>。你確定要改變這個看板最低角色?", | ||||
|   "shareBoard.confirm-change-team-role.confirmBtnText": "改變最小的看板規則", | ||||
|   "shareBoard.confirm-change-team-role.title": "改變最小的看板規則", | ||||
|   "shareBoard.confirm-link-channel": "連接看板到頻道", | ||||
|   "shareBoard.confirm-link-channel-button": "連接頻道", | ||||
|   "shareBoard.confirm-link-channel-button-with-other-channel": "解除連接或連接這", | ||||
|   "shareBoard.confirm-link-channel-subtext": "當你連接頻道到看板,該頻道所有成員(包含新的與現有的)都可以編輯,不包括訪客", | ||||
|   "shareBoard.confirm-link-channel-subtext": "當你連接頻道到看板,該頻道所有成員(包含新的與現有的)都可以編輯,不包括訪客。", | ||||
|   "shareBoard.confirm-link-channel-subtext-with-other-channel": "當你將一個頻道連接到看板時,該頻道的所有成員(現有的和新的)都可以編輯。並不包含訪客身分{lineBreak}看板目前正連接到另一個頻道。如果选择在這裡連接它,將取消另一個連接。", | ||||
|   "shareBoard.confirm-unlink.body": "當你取消頻道與看板連接,所有頻道成員(現在和新的)都將無法失去查看權限,除非單獨獲得許可。", | ||||
|   "shareBoard.confirm-unlink.confirmBtnText": "解除連結頻道", | ||||
|   "shareBoard.confirm-unlink.title": "從看板上取消頻道連接", | ||||
|   "shareBoard.lastAdmin": "看板必須有一位管理者", | ||||
|   "shareBoard.members-select-group": "會員", | ||||
|   "shareBoard.unknown-channel-display-name": "未知管道", | ||||
|   "tutorial_tip.finish_tour": "完成", | ||||
|   "tutorial_tip.got_it": "了解", | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. | ||||
| // See LICENSE.txt for license information. | ||||
| import {Utils} from './utils' | ||||
| import {Card} from './blocks/card' | ||||
| import {IPropertyTemplate, IPropertyOption, BoardGroup} from './blocks/board' | ||||
|  | ||||
| @@ -17,7 +16,7 @@ function groupCardsByOptions(cards: Card[], optionIds: string[], groupByProperty | ||||
|                 } | ||||
|                 groups.push(group) | ||||
|             } else { | ||||
|                 Utils.logError(`groupCardsByOptions: Missing option with id: ${optionId}`) | ||||
|                 // if optionId not found, its an old (deleted) option that can be ignored | ||||
|             } | ||||
|         } else { | ||||
|             // Empty group | ||||
|   | ||||
| @@ -280,6 +280,10 @@ describe('src/cardFilter', () => { | ||||
|         } | ||||
|  | ||||
|         test('verify isBefore clause', () => { | ||||
|             const filterClauseIsBeforeEmpty = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: []}) | ||||
|             const resulta = CardFilter.isClauseMet(filterClauseIsBeforeEmpty, [template], dateCard) | ||||
|             expect(resulta).toBeTruthy() | ||||
|  | ||||
|             const filterClauseIsBefore = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: [beforeRange.toString()]}) | ||||
|             const result = CardFilter.isClauseMet(filterClauseIsBefore, [template], dateCard) | ||||
|             expect(result).toBeFalsy() | ||||
| @@ -294,6 +298,10 @@ describe('src/cardFilter', () => { | ||||
|         }) | ||||
|  | ||||
|         test('verify isAfter clauses', () => { | ||||
|             const filterClauseIsAfterEmpty = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: []}) | ||||
|             const resulta = CardFilter.isClauseMet(filterClauseIsAfterEmpty, [template], dateCard) | ||||
|             expect(resulta).toBeTruthy() | ||||
|  | ||||
|             const filterClauseIsAfter = createFilterClause({propertyId: 'datePropertyID', condition: 'isAfter', values: [afterRange.toString()]}) | ||||
|             const result = CardFilter.isClauseMet(filterClauseIsAfter, [template], dateCard) | ||||
|             expect(result).toBeFalsy() | ||||
| @@ -308,6 +316,10 @@ describe('src/cardFilter', () => { | ||||
|         }) | ||||
|  | ||||
|         test('verify is clause', () => { | ||||
|             const filterClauseIsEmpty = createFilterClause({propertyId: 'datePropertyID', condition: 'isBefore', values: []}) | ||||
|             const resulta = CardFilter.isClauseMet(filterClauseIsEmpty, [template], dateCard) | ||||
|             expect(resulta).toBeTruthy() | ||||
|  | ||||
|             const filterClauseIsBefore = createFilterClause({propertyId: 'datePropertyID', condition: 'is', values: [beforeRange.toString()]}) | ||||
|             const result = CardFilter.isClauseMet(filterClauseIsBefore, [template], dateCard) | ||||
|             expect(result).toBeFalsy() | ||||
|   | ||||
| @@ -175,6 +175,9 @@ class CardFilter { | ||||
|             return !(value as string || '').endsWith(filter.values[0]?.toLowerCase()) | ||||
|         } | ||||
|         case 'isBefore': { | ||||
|             if (filter.values.length === 0) { | ||||
|                 return true | ||||
|             } | ||||
|             if (dateValue !== undefined) { | ||||
|                 const numericFilter = parseInt(filter.values[0], 10) | ||||
|                 if (template && (template.type === 'createdTime' || template.type === 'updatedTime')) { | ||||
| @@ -192,6 +195,9 @@ class CardFilter { | ||||
|             return false | ||||
|         } | ||||
|         case 'isAfter': { | ||||
|             if (filter.values.length === 0) { | ||||
|                 return true | ||||
|             } | ||||
|             if (dateValue !== undefined) { | ||||
|                 const numericFilter = parseInt(filter.values[0], 10) | ||||
|                 if (template && (template.type === 'createdTime' || template.type === 'updatedTime')) { | ||||
|   | ||||
| @@ -94,6 +94,7 @@ const GlobalHeaderSettingsMenu = (props: Props) => { | ||||
|                         name={intl.formatMessage({id: 'Sidebar.random-icons', defaultMessage: 'Random icons'})} | ||||
|                         isOn={randomIcons} | ||||
|                         onClick={async () => toggleRandomIcons()} | ||||
|                         suppressItemClicked={true} | ||||
|                     /> | ||||
|                     {me?.is_guest !== true && | ||||
|                         <Menu.Text | ||||
|   | ||||
| @@ -163,6 +163,7 @@ const SidebarSettingsMenu = (props: Props) => { | ||||
|                         name={intl.formatMessage({id: 'Sidebar.random-icons', defaultMessage: 'Random icons'})} | ||||
|                         isOn={randomIcons} | ||||
|                         onClick={async () => toggleRandomIcons()} | ||||
|                         suppressItemClicked={true} | ||||
|                     /> | ||||
|                 </Menu> | ||||
|             </MenuWrapper> | ||||
|   | ||||
| @@ -124,7 +124,7 @@ | ||||
|         </ul> | ||||
|         <div class="secondary-footer__copy"> | ||||
|             <small class="disclaimer"><span class="mm-copyright" style="margin-right: 1rem;">© Mattermost, Inc. | ||||
|                     2022.</span> <span><a href="https://mattermost.com/terms-of-use/" title="Terms of Service">Terms | ||||
|                     2023.</span> <span><a href="https://mattermost.com/terms-of-use/" title="Terms of Service">Terms | ||||
|                         of Use</a> <span style="margin: 0 0.5rem;">|</span> <a | ||||
|                         href="https://mattermost.com/privacy-policy/" title="Privacy Policy">Privacy Policy</a> <span | ||||
|                         style="margin: 0 0.5rem;">|</span> <a href="https://mattermost.com/privacy-policy/cookies/" | ||||
| @@ -186,4 +186,4 @@ | ||||
| <script src="https://code.jquery.com/jquery-3.5.1.min.js" | ||||
|     integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> | ||||
| <script src="{{ "js/tabs.js" | absURL }}"></script> | ||||
| <script src="{{ "js/main.js" | absURL }}"></script> | ||||
| <script src="{{ "js/main.js" | absURL }}"></script> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user