From 68f5130098286e156156ee2a01e1012a9e1b4800 Mon Sep 17 00:00:00 2001 From: Chen-I Lim Date: Fri, 18 Dec 2020 12:52:45 -0800 Subject: [PATCH] Refactor: card contentOrder --- webapp/.eslintrc.json | 3 +- webapp/i18n/en.json | 13 ++++ webapp/src/blocks/block.test.ts | 3 - webapp/src/blocks/card.ts | 10 +++ webapp/src/blocks/contentBlock.ts | 10 +++ webapp/src/blocks/dividerBlock.tsx | 6 +- webapp/src/blocks/imageBlock.ts | 6 +- webapp/src/blocks/orderedBlock.ts | 25 ------- webapp/src/blocks/textBlock.ts | 6 +- webapp/src/components/cardDetail.tsx | 46 ++++++++---- webapp/src/components/contentBlock.tsx | 96 +++++++++++++++----------- webapp/src/mutator.ts | 20 +++--- webapp/src/octoUtils.tsx | 32 +++++---- webapp/src/test/testBlockFactory.ts | 3 - webapp/src/utils.ts | 8 +++ webapp/src/viewModel/cardTree.test.ts | 15 +++- webapp/src/viewModel/cardTree.ts | 10 +-- 17 files changed, 186 insertions(+), 126 deletions(-) create mode 100644 webapp/src/blocks/contentBlock.ts delete mode 100644 webapp/src/blocks/orderedBlock.ts diff --git a/webapp/.eslintrc.json b/webapp/.eslintrc.json index bc60529d8..73bab3367 100644 --- a/webapp/.eslintrc.json +++ b/webapp/.eslintrc.json @@ -58,7 +58,8 @@ } ], "react/no-string-refs": 2, - "no-only-tests/no-only-tests": ["error", {"focus": ["only", "skip"]}] + "no-only-tests/no-only-tests": ["error", {"focus": ["only", "skip"]}], + "max-nested-callbacks": ["error", {"max": 5}] }, "overrides": [ { diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 7f3c7e430..16db2df2d 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -13,6 +13,7 @@ "CardDetail.add-content": "Add content", "CardDetail.add-icon": "Add icon", "CardDetail.add-property": "+ Add a property", + "CardDetail.addCardText": "add card text", "CardDetail.image": "Image", "CardDetail.new-comment-placeholder": "Add a comment...", "CardDetail.text": "Text", @@ -20,6 +21,18 @@ "CardDialog.nocard": "This card doesn't exist or is inaccessible", "Comment.delete": "Delete", "CommentsList.send": "Send", + "ContentBlock.Delete": "Delete", + "ContentBlock.DeleteAction": "delete", + "ContentBlock.Text": "Text", + "ContentBlock.addDivider": "Add divider", + "ContentBlock.addImage": "Add image", + "ContentBlock.addText": "Add text", + "ContentBlock.divider": "Divider", + "ContentBlock.editCardText": "edit card text", + "ContentBlock.editText": "Edit text...", + "ContentBlock.insertAbove": "Insert above", + "ContentBlock.moveDown": "Move down", + "ContentBlock.moveUp": "Move up", "Filter.includes": "includes", "Filter.is-empty": "is empty", "Filter.is-not-empty": "is not empty", diff --git a/webapp/src/blocks/block.test.ts b/webapp/src/blocks/block.test.ts index 689f530ef..ed8995452 100644 --- a/webapp/src/blocks/block.test.ts +++ b/webapp/src/blocks/block.test.ts @@ -63,7 +63,6 @@ test('block: clone text', async () => { const blockB = new MutableTextBlock(blockA) expect(blockB).toEqual(blockA) - expect(blockB.order).toEqual(blockA.order) }) test('block: clone image', async () => { @@ -72,7 +71,6 @@ test('block: clone image', async () => { const blockB = new MutableImageBlock(blockA) expect(blockB).toEqual(blockA) - expect(blockB.order).toEqual(blockA.order) expect(blockB.url.length).toBeGreaterThan(0) expect(blockB.url).toEqual(blockA.url) }) @@ -83,5 +81,4 @@ test('block: clone divider', async () => { const blockB = new MutableDividerBlock(blockA) expect(blockB).toEqual(blockA) - expect(blockB.order).toEqual(blockA.order) }) diff --git a/webapp/src/blocks/card.ts b/webapp/src/blocks/card.ts index f163ceeb5..622f5afe7 100644 --- a/webapp/src/blocks/card.ts +++ b/webapp/src/blocks/card.ts @@ -9,6 +9,8 @@ interface Card extends IBlock { readonly icon: string readonly isTemplate: boolean readonly properties: Readonly> + readonly contentOrder: readonly string[] + duplicate(): MutableCard } @@ -34,12 +36,20 @@ class MutableCard extends MutableBlock { this.fields.properties = value } + get contentOrder(): string[] { + return this.fields.contentOrder + } + set contentOrder(value: string[]) { + this.fields.contentOrder = value + } + constructor(block: any = {}) { super(block) this.type = 'card' this.icon = block.fields?.icon || '' this.properties = {...(block.fields?.properties || {})} + this.contentOrder = block.fields?.contentOrder?.slice() || [] } duplicate(): MutableCard { diff --git a/webapp/src/blocks/contentBlock.ts b/webapp/src/blocks/contentBlock.ts new file mode 100644 index 000000000..0cbe78c0e --- /dev/null +++ b/webapp/src/blocks/contentBlock.ts @@ -0,0 +1,10 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import {IBlock, MutableBlock} from './block' + +type IContentBlock = IBlock + +class MutableContentBlock extends MutableBlock implements IContentBlock { +} + +export {IContentBlock, MutableContentBlock} diff --git a/webapp/src/blocks/dividerBlock.tsx b/webapp/src/blocks/dividerBlock.tsx index 78abb5be6..4d02678c2 100644 --- a/webapp/src/blocks/dividerBlock.tsx +++ b/webapp/src/blocks/dividerBlock.tsx @@ -1,10 +1,10 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {IOrderedBlock, MutableOrderedBlock} from './orderedBlock' +import {IContentBlock, MutableContentBlock} from './contentBlock' -type DividerBlock = IOrderedBlock +type DividerBlock = IContentBlock -class MutableDividerBlock extends MutableOrderedBlock { +class MutableDividerBlock extends MutableContentBlock { constructor(block: any = {}) { super(block) this.type = 'divider' diff --git a/webapp/src/blocks/imageBlock.ts b/webapp/src/blocks/imageBlock.ts index 1a62856a8..cc97fbdb8 100644 --- a/webapp/src/blocks/imageBlock.ts +++ b/webapp/src/blocks/imageBlock.ts @@ -1,12 +1,12 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {IOrderedBlock, MutableOrderedBlock} from './orderedBlock' +import {IContentBlock, MutableContentBlock} from './contentBlock' -interface ImageBlock extends IOrderedBlock { +interface ImageBlock extends IContentBlock { readonly url: string } -class MutableImageBlock extends MutableOrderedBlock implements IOrderedBlock { +class MutableImageBlock extends MutableContentBlock implements IContentBlock { get url(): string { return this.fields.url as string } diff --git a/webapp/src/blocks/orderedBlock.ts b/webapp/src/blocks/orderedBlock.ts deleted file mode 100644 index 861991c14..000000000 --- a/webapp/src/blocks/orderedBlock.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import {IBlock} from '../blocks/block' - -import {MutableBlock} from './block' - -interface IOrderedBlock extends IBlock { - readonly order: number -} - -class MutableOrderedBlock extends MutableBlock implements IOrderedBlock { - get order(): number { - return this.fields.order as number - } - set order(value: number) { - this.fields.order = value - } - - constructor(block: any = {}) { - super(block) - this.order = block.fields?.order || 0 - } -} - -export {IOrderedBlock, MutableOrderedBlock} diff --git a/webapp/src/blocks/textBlock.ts b/webapp/src/blocks/textBlock.ts index 7a5b647c1..40c464f24 100644 --- a/webapp/src/blocks/textBlock.ts +++ b/webapp/src/blocks/textBlock.ts @@ -1,10 +1,10 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {IOrderedBlock, MutableOrderedBlock} from './orderedBlock' +import {IContentBlock, MutableContentBlock} from './contentBlock' -type TextBlock = IOrderedBlock +type TextBlock = IContentBlock -class MutableTextBlock extends MutableOrderedBlock { +class MutableTextBlock extends MutableContentBlock { constructor(block: any = {}) { super(block) this.type = 'text' diff --git a/webapp/src/components/cardDetail.tsx b/webapp/src/components/cardDetail.tsx index 4c28b0782..5261f2d02 100644 --- a/webapp/src/components/cardDetail.tsx +++ b/webapp/src/components/cardDetail.tsx @@ -89,12 +89,7 @@ class CardDetail extends React.Component { placeholderText='Add a description...' onBlur={(text) => { if (text) { - const block = new MutableTextBlock() - block.parentId = card.id - block.rootId = card.rootId - block.title = text - block.order = (this.props.cardTree.contents.length + 1) * 1000 - mutator.insertBlock(block, 'add card text') + this.addTextBlock(text) } }} /> @@ -230,19 +225,24 @@ class CardDetail extends React.Component { id='text' name={intl.formatMessage({id: 'CardDetail.text', defaultMessage: 'Text'})} onClick={() => { - const block = new MutableTextBlock() - block.parentId = card.id - block.rootId = card.rootId - block.order = (this.props.cardTree.contents.length + 1) * 1000 - mutator.insertBlock(block, 'add text') + this.addTextBlock('') }} /> Utils.selectLocalFile( - (file) => mutator.createImageBlock(card, file, (this.props.cardTree.contents.length + 1) * 1000), - '.jpg,.jpeg,.png', + onClick={() => Utils.selectLocalFile((file) => { + mutator.performAsUndoGroup(async () => { + const description = intl.formatMessage({id: 'ContentBlock.addImage', defaultMessage: 'add image'}) + const newBlock = await mutator.createImageBlock(card, file, description) + if (newBlock) { + const contentOrder = card.contentOrder.slice() + contentOrder.push(newBlock.id) + await mutator.changeCardContentOrder(card, contentOrder, description) + } + }) + }, + '.jpg,.jpeg,.png', )} /> @@ -253,6 +253,24 @@ class CardDetail extends React.Component { ) } + + private addTextBlock(text: string): void { + const {intl, cardTree} = this.props + const {card} = cardTree + + const block = new MutableTextBlock() + block.parentId = card.id + block.rootId = card.rootId + block.title = text + + const contentOrder = card.contentOrder.slice() + contentOrder.push(block.id) + mutator.performAsUndoGroup(async () => { + const description = intl.formatMessage({id: 'CardDetail.addCardText', defaultMessage: 'add card text'}) + await mutator.insertBlock(block, description) + await mutator.changeCardContentOrder(card, contentOrder, description) + }) + } } export default injectIntl(CardDetail) diff --git a/webapp/src/components/contentBlock.tsx b/webapp/src/components/contentBlock.tsx index 91ae675e6..3d91e8cd9 100644 --- a/webapp/src/components/contentBlock.tsx +++ b/webapp/src/components/contentBlock.tsx @@ -2,13 +2,13 @@ // See LICENSE.txt for license information. import React from 'react' +import {injectIntl, IntlShape} from 'react-intl' -import {IBlock} from '../blocks/block' +import {Card} from '../blocks/card' +import {IContentBlock} from '../blocks/contentBlock' import {MutableDividerBlock} from '../blocks/dividerBlock' -import {IOrderedBlock} from '../blocks/orderedBlock' import {MutableTextBlock} from '../blocks/textBlock' import mutator from '../mutator' -import {OctoUtils} from '../octoUtils' import {Utils} from '../utils' import IconButton from '../widgets/buttons/iconButton' import AddIcon from '../widgets/icons/add' @@ -26,15 +26,16 @@ import './contentBlock.scss' import {MarkdownEditor} from './markdownEditor' type Props = { - block: IOrderedBlock - card: IBlock - contents: readonly IOrderedBlock[] + block: IContentBlock + card: Card + contents: readonly IContentBlock[] readonly: boolean + intl: IntlShape } class ContentBlock extends React.PureComponent { public render(): JSX.Element | null { - const {card, contents, block} = this.props + const {intl, card, contents, block} = this.props if (block.type !== 'text' && block.type !== 'image' && block.type !== 'divider') { Utils.assertFailure(`Block type is unknown: ${block.type}`) @@ -52,45 +53,46 @@ class ContentBlock extends React.PureComponent { {index > 0 && } onClick={() => { - const previousBlock = contents[index - 1] - const newOrder = OctoUtils.getOrderBefore(previousBlock, contents) - Utils.log(`moveUp ${newOrder}`) - mutator.changeOrder(block, newOrder, 'move up') + const contentOrder = contents.map((o) => o.id) + Utils.arrayMove(contentOrder, index, index - 1) + mutator.changeCardContentOrder(card, contentOrder) }} />} {index < (contents.length - 1) && } onClick={() => { - const nextBlock = contents[index + 1] - const newOrder = OctoUtils.getOrderAfter(nextBlock, contents) - Utils.log(`moveDown ${newOrder}`) - mutator.changeOrder(block, newOrder, 'move down') + const contentOrder = contents.map((o) => o.id) + Utils.arrayMove(contentOrder, index, index + 1) + mutator.changeCardContentOrder(card, contentOrder) }} />} } > } onClick={() => { const newBlock = new MutableTextBlock() newBlock.parentId = card.id newBlock.rootId = card.rootId - // TODO: Handle need to reorder all blocks - newBlock.order = OctoUtils.getOrderBefore(block, contents) - Utils.log(`insert block ${block.id}, order: ${block.order}`) - mutator.insertBlock(newBlock, 'insert card text') + const contentOrder = contents.map((o) => o.id) + contentOrder.splice(index, 0, newBlock.id) + mutator.performAsUndoGroup(async () => { + const description = intl.formatMessage({id: 'ContentBlock.addText', defaultMessage: 'Add text'}) + await mutator.insertBlock(newBlock, description) + await mutator.changeCardContentOrder(card, contentOrder, description) + }) }} /> { name='Image' icon={} onClick={() => { - Utils.selectLocalFile( - (file) => { - mutator.createImageBlock(card, file, OctoUtils.getOrderBefore(block, contents)) - }, - '.jpg,.jpeg,.png') + Utils.selectLocalFile((file) => { + mutator.performAsUndoGroup(async () => { + const description = intl.formatMessage({id: 'ContentBlock.addImage', defaultMessage: 'Add image'}) + const newBlock = await mutator.createImageBlock(card, file, description) + if (newBlock) { + const contentOrder = contents.map((o) => o.id) + contentOrder.splice(index, 0, newBlock.id) + await mutator.changeCardContentOrder(card, contentOrder, description) + } + }) + }, + '.jpg,.jpeg,.png') }} /> } onClick={() => { const newBlock = new MutableDividerBlock() newBlock.parentId = card.id newBlock.rootId = card.rootId - // TODO: Handle need to reorder all blocks - newBlock.order = OctoUtils.getOrderBefore(block, contents) - Utils.log(`insert block ${block.id}, order: ${block.order}`) - mutator.insertBlock(newBlock, 'insert card text') + const contentOrder = contents.map((o) => o.id) + contentOrder.splice(index, 0, newBlock.id) + mutator.performAsUndoGroup(async () => { + const description = intl.formatMessage({id: 'ContentBlock.addDivider', defaultMessage: 'Add divider'}) + await mutator.insertBlock(newBlock, description) + await mutator.changeCardContentOrder(card, contentOrder, description) + }) }} /> } id='delete' - name='Delete' - onClick={() => mutator.deleteBlock(block)} + name={intl.formatMessage({id: 'ContentBlock.Delete', defaultMessage: 'Delete'})} + onClick={() => { + const description = intl.formatMessage({id: 'ContentBlock.DeleteAction', defaultMessage: 'delete'}) + const contentOrder = contents.map((o) => o.id).filter((o) => o !== block.id) + mutator.performAsUndoGroup(async () => { + await mutator.deleteBlock(block, description) + await mutator.changeCardContentOrder(card, contentOrder, description) + }) + }} /> @@ -134,10 +153,9 @@ class ContentBlock extends React.PureComponent { {block.type === 'text' && { - Utils.log(`change text ${block.id}, ${text}`) - mutator.changeTitle(block, text, 'edit card text') + mutator.changeTitle(block, text, intl.formatMessage({id: 'ContentBlock.editCardText', defaultMessage: 'edit card text'})) }} readonly={this.props.readonly} />} @@ -152,4 +170,4 @@ class ContentBlock extends React.PureComponent { } } -export default ContentBlock +export default injectIntl(ContentBlock) diff --git a/webapp/src/mutator.ts b/webapp/src/mutator.ts index 7ad796766..4a5156410 100644 --- a/webapp/src/mutator.ts +++ b/webapp/src/mutator.ts @@ -1,18 +1,17 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {BlockIcons} from './blockIcons' import {IBlock, MutableBlock} from './blocks/block' import {Board, IPropertyOption, IPropertyTemplate, MutableBoard, PropertyType} from './blocks/board' import {BoardView, ISortOption, MutableBoardView} from './blocks/boardView' import {Card, MutableCard} from './blocks/card' import {MutableImageBlock} from './blocks/imageBlock' -import {IOrderedBlock, MutableOrderedBlock} from './blocks/orderedBlock' -import {BoardTree} from './viewModel/boardTree' import {FilterGroup} from './filterGroup' import octoClient from './octoClient' +import {OctoUtils} from './octoUtils' import undoManager from './undomanager' import {Utils} from './utils' -import {OctoUtils} from './octoUtils' -import {BlockIcons} from './blockIcons' +import {BoardTree} from './viewModel/boardTree' // // The Mutator is used to make all changes to server state @@ -173,10 +172,10 @@ class Mutator { await this.updateBlock(newBoard, board, actionDescription) } - async changeOrder(block: IOrderedBlock, order: number, description = 'change order') { - const newBlock = new MutableOrderedBlock(block) - newBlock.order = order - await this.updateBlock(newBlock, block, description) + async changeCardContentOrder(card: Card, contentOrder: string[], description = 'reorder'): Promise { + const newCard = new MutableCard(card) + newCard.contentOrder = contentOrder + await this.updateBlock(newCard, card, description) } // Property Templates @@ -583,7 +582,7 @@ class Mutator { return octoClient.importFullArchive(blocks) } - async createImageBlock(parent: IBlock, file: File, order = 1000): Promise { + async createImageBlock(parent: IBlock, file: File, description = 'add image'): Promise { const url = await octoClient.uploadFile(file) if (!url) { return undefined @@ -592,7 +591,6 @@ class Mutator { const block = new MutableImageBlock() block.parentId = parent.id block.rootId = parent.rootId - block.order = order block.url = url await undoManager.perform( @@ -602,7 +600,7 @@ class Mutator { async () => { await octoClient.deleteBlock(block.id) }, - 'add image', + description, this.undoGroupId, ) diff --git a/webapp/src/octoUtils.tsx b/webapp/src/octoUtils.tsx index c05b0d535..3a9cc414b 100644 --- a/webapp/src/octoUtils.tsx +++ b/webapp/src/octoUtils.tsx @@ -8,7 +8,6 @@ import {MutableCard} from './blocks/card' import {MutableCommentBlock} from './blocks/commentBlock' import {MutableDividerBlock} from './blocks/dividerBlock' import {MutableImageBlock} from './blocks/imageBlock' -import {IOrderedBlock} from './blocks/orderedBlock' import {MutableTextBlock} from './blocks/textBlock' import {Utils} from './utils' @@ -42,22 +41,27 @@ class OctoUtils { return displayValue } - static getOrderBefore(block: IOrderedBlock, blocks: readonly IOrderedBlock[]): number { - const index = blocks.indexOf(block) - if (index === 0) { - return block.order / 2 + static relativeBlockOrder(partialOrder: readonly string[], blocks: readonly IBlock[], blockA: IBlock, blockB: IBlock): number { + const orderA = partialOrder.indexOf(blockA.id) + const orderB = partialOrder.indexOf(blockB.id) + + if (orderA >= 0 && orderB >= 0) { + // Order of both blocks is specified + return orderA - orderB } - const previousBlock = blocks[index - 1] - return (block.order + previousBlock.order) / 2 + if (orderA >= 0) { + return -1 + } + if (orderB >= 0) { + return 1 + } + + // Order of both blocks are unspecified, use create date + return blockA.createAt - blockB.createAt } - static getOrderAfter(block: IOrderedBlock, blocks: readonly IOrderedBlock[]): number { - const index = blocks.indexOf(block) - if (index === blocks.length - 1) { - return block.order + 1000 - } - const nextBlock = blocks[index + 1] - return (block.order + nextBlock.order) / 2 + static getBlockOrder(partialOrder: readonly string[], blocks: readonly IBlock[]): IBlock[] { + return blocks.slice().sort((a, b) => this.relativeBlockOrder(partialOrder, blocks, a, b)) } static hydrateBlock(block: IBlock): MutableBlock { diff --git a/webapp/src/test/testBlockFactory.ts b/webapp/src/test/testBlockFactory.ts index 34672197e..f24b1495e 100644 --- a/webapp/src/test/testBlockFactory.ts +++ b/webapp/src/test/testBlockFactory.ts @@ -99,7 +99,6 @@ class TestBlockFactory { block.parentId = card.id block.rootId = card.rootId block.title = 'title' - block.order = 100 return block } @@ -109,7 +108,6 @@ class TestBlockFactory { block.parentId = card.id block.rootId = card.rootId block.url = 'url' - block.order = 100 return block } @@ -119,7 +117,6 @@ class TestBlockFactory { block.parentId = card.id block.rootId = card.rootId block.title = 'title' - block.order = 100 return block } diff --git a/webapp/src/utils.ts b/webapp/src/utils.ts index 7d3c64f3f..0409f6fa9 100644 --- a/webapp/src/utils.ts +++ b/webapp/src/utils.ts @@ -76,6 +76,10 @@ class Utils { return text } + static sleep(miliseconds: number): Promise { + return new Promise((resolve) => setTimeout(resolve, miliseconds)) + } + // Errors static assertValue(valueObject: any): void { @@ -200,6 +204,10 @@ class Utils { } return true } + + static arrayMove(arr: any[], srcIndex: number, destIndex: number): void { + arr.splice(destIndex, 0, arr.splice(srcIndex, 1)[0]) + } } export {Utils} diff --git a/webapp/src/viewModel/cardTree.test.ts b/webapp/src/viewModel/cardTree.test.ts index 288d219e1..9ee39a407 100644 --- a/webapp/src/viewModel/cardTree.test.ts +++ b/webapp/src/viewModel/cardTree.test.ts @@ -8,6 +8,8 @@ import 'isomorphic-fetch' import {TestBlockFactory} from '../test/testBlockFactory' import {FetchMock} from '../test/fetchMock' +import {Utils} from '../utils' + import {CardTree, MutableCardTree} from './cardTree' global.fetch = FetchMock.fn @@ -20,9 +22,14 @@ test('CardTree', async () => { const card = TestBlockFactory.createCard() expect(card.id).not.toBeNull() const comment = TestBlockFactory.createComment(card) + + // Content const text = TestBlockFactory.createText(card) + await Utils.sleep(10) const image = TestBlockFactory.createImage(card) + await Utils.sleep(10) const divider = TestBlockFactory.createDivider(card) + card.contentOrder = [image.id, divider.id, text.id] let cardTree: CardTree | undefined @@ -41,12 +48,14 @@ test('CardTree', async () => { expect(FetchMock.fn).toBeCalledTimes(2) expect(cardTree.card).toEqual(card) expect(cardTree.comments).toEqual([comment]) - expect(cardTree.contents).toEqual([text, image, divider]) + expect(cardTree.contents).toEqual([image, divider, text]) // Must match specified card.contentOrder // Incremental update const comment2 = TestBlockFactory.createComment(card) const text2 = TestBlockFactory.createText(card) + await Utils.sleep(10) const image2 = TestBlockFactory.createImage(card) + await Utils.sleep(10) const divider2 = TestBlockFactory.createDivider(card) cardTree = MutableCardTree.incrementalUpdate(cardTree, [comment2, text2, image2, divider2]) @@ -55,7 +64,9 @@ test('CardTree', async () => { fail('incrementalUpdate') } expect(cardTree.comments).toEqual([comment, comment2]) - expect(cardTree.contents).toEqual([text, image, divider, text2, image2, divider2]) + + // The added content's order was not specified in card.contentOrder, so much match created date order + expect(cardTree.contents).toEqual([image, divider, text, text2, image2, divider2]) // Incremental update: No change const anotherCard = TestBlockFactory.createCard() diff --git a/webapp/src/viewModel/cardTree.ts b/webapp/src/viewModel/cardTree.ts index 16db04e87..ce8f19c8d 100644 --- a/webapp/src/viewModel/cardTree.ts +++ b/webapp/src/viewModel/cardTree.ts @@ -2,21 +2,21 @@ // See LICENSE.txt for license information. import {IBlock} from '../blocks/block' import {Card, MutableCard} from '../blocks/card' -import {IOrderedBlock} from '../blocks/orderedBlock' +import {IContentBlock} from '../blocks/contentBlock' import octoClient from '../octoClient' import {OctoUtils} from '../octoUtils' interface CardTree { readonly card: Card readonly comments: readonly IBlock[] - readonly contents: readonly IOrderedBlock[] + readonly contents: readonly IContentBlock[] readonly allBlocks: readonly IBlock[] } class MutableCardTree implements CardTree { card: MutableCard comments: IBlock[] = [] - contents: IOrderedBlock[] = [] + contents: IContentBlock[] = [] get allBlocks(): IBlock[] { return [this.card, ...this.comments, ...this.contents] @@ -55,8 +55,8 @@ class MutableCardTree implements CardTree { filter((block) => block.type === 'comment'). sort((a, b) => a.createAt - b.createAt) - const contentBlocks = blocks.filter((block) => block.type === 'text' || block.type === 'image' || block.type === 'divider') as IOrderedBlock[] - cardTree.contents = contentBlocks.sort((a, b) => a.order - b.order) + const contentBlocks = blocks.filter((block) => block.type === 'text' || block.type === 'image' || block.type === 'divider') as IContentBlock[] + cardTree.contents = OctoUtils.getBlockOrder(card.contentOrder, contentBlocks) return cardTree }