mirror of
https://github.com/mattermost/focalboard.git
synced 2024-12-12 09:04:14 +02:00
Description Section of Card can now Have Columns (#637)
* Grid Layout * add margin * add margin * more work * fix linting * fix alignment * update viewing * fix editing * wip * some wip fix * fix stuff * fix linting * fix type errors * fix render of image * fixl inting * fix tests * fix linting * fix tests * fix eslint * remove ref * fix colIndex * address PR comments Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com>
This commit is contained in:
parent
d6f760b06b
commit
2ea4a85495
@ -8,7 +8,7 @@ interface Card extends IBlock {
|
||||
readonly icon: string
|
||||
readonly isTemplate: boolean
|
||||
readonly properties: Readonly<Record<string, string | string[]>>
|
||||
readonly contentOrder: readonly string[]
|
||||
readonly contentOrder: Readonly<Array<string | string[]>>
|
||||
|
||||
duplicate(): MutableCard
|
||||
}
|
||||
@ -35,10 +35,10 @@ class MutableCard extends MutableBlock implements Card {
|
||||
this.fields.properties = value
|
||||
}
|
||||
|
||||
get contentOrder(): string[] {
|
||||
get contentOrder(): Array<string | string[]> {
|
||||
return this.fields.contentOrder
|
||||
}
|
||||
set contentOrder(value: string[]) {
|
||||
set contentOrder(value: Array<string | string[]>) {
|
||||
this.fields.contentOrder = value
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
import {IBlock, MutableBlock} from './block'
|
||||
|
||||
type IContentBlock = IBlock
|
||||
type IContentBlockWithCords = {block: IBlock, cords: {x: number, y?: number, z?: number}}
|
||||
|
||||
class MutableContentBlock extends MutableBlock implements IContentBlock {
|
||||
}
|
||||
|
||||
export {IContentBlock, MutableContentBlock}
|
||||
export {IContentBlock, IContentBlockWithCords, MutableContentBlock}
|
||||
|
@ -6,7 +6,6 @@ import {useIntl} from 'react-intl'
|
||||
|
||||
import {BlockTypes} from '../blocks/block'
|
||||
import {Card} from '../blocks/card'
|
||||
import {IContentBlock} from '../blocks/contentBlock'
|
||||
import mutator from '../mutator'
|
||||
import {Utils} from '../utils'
|
||||
import Menu from '../widgets/menu'
|
||||
@ -15,14 +14,14 @@ import {contentRegistry} from './content/contentRegistry'
|
||||
|
||||
type Props = {
|
||||
type: BlockTypes
|
||||
block: IContentBlock
|
||||
card: Card
|
||||
contents: readonly IContentBlock[]
|
||||
cords: {x: number, y?: number, z?: number}
|
||||
}
|
||||
|
||||
const AddContentMenuItem = React.memo((props:Props): JSX.Element => {
|
||||
const {card, contents, block, type} = props
|
||||
const index = contents.indexOf(block)
|
||||
const {card, type, cords} = props
|
||||
const index = cords.x
|
||||
const contentOrder = card.contentOrder.slice()
|
||||
const intl = useIntl()
|
||||
|
||||
const handler = contentRegistry.getHandler(type)
|
||||
@ -42,7 +41,6 @@ const AddContentMenuItem = React.memo((props:Props): JSX.Element => {
|
||||
newBlock.parentId = card.id
|
||||
newBlock.rootId = card.rootId
|
||||
|
||||
const contentOrder = contents.map((o) => o.id)
|
||||
contentOrder.splice(index, 0, newBlock.id)
|
||||
const typeName = handler.getDisplayText(intl)
|
||||
const description = intl.formatMessage({id: 'ContentBlock.addElement', defaultMessage: 'add {type}'}, {type: typeName})
|
||||
|
@ -3,15 +3,18 @@
|
||||
import React from 'react'
|
||||
import {useIntl, IntlShape} from 'react-intl'
|
||||
|
||||
import {IContentBlock} from '../../blocks/contentBlock'
|
||||
import {IContentBlockWithCords, IContentBlock} from '../../blocks/contentBlock'
|
||||
import {MutableTextBlock} from '../../blocks/textBlock'
|
||||
import mutator from '../../mutator'
|
||||
import {CardTree} from '../../viewModel/cardTree'
|
||||
import {Card} from '../../blocks/card'
|
||||
import {useSortableWithGrip} from '../../hooks/sortable'
|
||||
|
||||
import ContentBlock from '../contentBlock'
|
||||
import {MarkdownEditor} from '../markdownEditor'
|
||||
|
||||
export type Position = 'left' | 'right' | 'above' | 'below' | 'aboveRow' | 'belowRow'
|
||||
|
||||
type Props = {
|
||||
cardTree: CardTree
|
||||
readonly: boolean
|
||||
@ -32,15 +35,62 @@ function addTextBlock(card: Card, intl: IntlShape, text: string): void {
|
||||
})
|
||||
}
|
||||
|
||||
function moveBlock(card: Card, srcBlock: IContentBlock, dstBlock: IContentBlock, intl: IntlShape): void {
|
||||
let contentOrder = card.contentOrder.slice()
|
||||
const isDraggingDown = contentOrder.indexOf(srcBlock.id) <= contentOrder.indexOf(dstBlock.id)
|
||||
contentOrder = contentOrder.filter((id) => srcBlock.id !== id)
|
||||
let destIndex = contentOrder.indexOf(dstBlock.id)
|
||||
if (isDraggingDown) {
|
||||
destIndex += 1
|
||||
function moveBlock(card: Card, srcBlock: IContentBlockWithCords, dstBlock: IContentBlockWithCords, intl: IntlShape, moveTo: Position): void {
|
||||
const contentOrder = card.contentOrder.slice()
|
||||
|
||||
const srcBlockId = srcBlock.block.id
|
||||
const dstBlockId = dstBlock.block.id
|
||||
|
||||
const srcBlockX = srcBlock.cords.x
|
||||
let dstBlockX = dstBlock.cords.x
|
||||
|
||||
const srcBlockY = (srcBlock.cords.y || srcBlock.cords.y === 0) && (srcBlock.cords.y > -1) ? srcBlock.cords.y : -1
|
||||
let dstBlockY = (dstBlock.cords.y || dstBlock.cords.y === 0) && (dstBlock.cords.y > -1) ? dstBlock.cords.y : -1
|
||||
|
||||
if (srcBlockId === dstBlockId) {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete Src Block
|
||||
if (srcBlockY > -1) {
|
||||
(contentOrder[srcBlockX] as string[]).splice(srcBlockY, 1)
|
||||
|
||||
if (contentOrder[srcBlockX].length === 1) {
|
||||
contentOrder.splice(srcBlockX, 1, contentOrder[srcBlockX][0])
|
||||
}
|
||||
} else {
|
||||
contentOrder.splice(srcBlockX, 1)
|
||||
|
||||
if (dstBlockX > srcBlockX) {
|
||||
dstBlockX -= 1
|
||||
}
|
||||
}
|
||||
|
||||
if (moveTo === 'right') {
|
||||
if (dstBlockY > -1) {
|
||||
if (dstBlockX === srcBlockX && dstBlockY > srcBlockY) {
|
||||
dstBlockY -= 1
|
||||
}
|
||||
|
||||
(contentOrder[dstBlockX] as string[]).splice(dstBlockY + 1, 0, srcBlockId)
|
||||
} else {
|
||||
contentOrder.splice(dstBlockX, 1, [dstBlockId, srcBlockId])
|
||||
}
|
||||
} else if (moveTo === 'left') {
|
||||
if (dstBlockY > -1) {
|
||||
if (dstBlockX === srcBlockX && dstBlockY > srcBlockY) {
|
||||
dstBlockY -= 1
|
||||
}
|
||||
|
||||
(contentOrder[dstBlockX] as string[]).splice(dstBlockY, 0, srcBlockId)
|
||||
} else {
|
||||
contentOrder.splice(dstBlockX, 1, [srcBlockId, dstBlockId])
|
||||
}
|
||||
} else if (moveTo === 'aboveRow') {
|
||||
contentOrder.splice(dstBlockX, 0, srcBlockId)
|
||||
} else if (moveTo === 'belowRow') {
|
||||
contentOrder.splice(dstBlockX + 1, 0, srcBlockId)
|
||||
}
|
||||
contentOrder.splice(destIndex, 0, srcBlock.id)
|
||||
|
||||
mutator.performAsUndoGroup(async () => {
|
||||
const description = intl.formatMessage({id: 'CardDetail.moveContent', defaultMessage: 'move card content'})
|
||||
@ -48,6 +98,82 @@ function moveBlock(card: Card, srcBlock: IContentBlock, dstBlock: IContentBlock,
|
||||
})
|
||||
}
|
||||
|
||||
type ContentBlockWithDragAndDropProps = {
|
||||
block: IContentBlock | IContentBlock[],
|
||||
x: number,
|
||||
card: Card,
|
||||
cardTree: CardTree,
|
||||
intl: IntlShape,
|
||||
readonly: boolean,
|
||||
}
|
||||
|
||||
const ContentBlockWithDragAndDrop = (props: ContentBlockWithDragAndDropProps) => {
|
||||
const [, isOver,, itemRef] = useSortableWithGrip('content', {block: props.block, cords: {x: props.x}}, true, (src, dst) => moveBlock(props.card, src, dst, props.intl, 'aboveRow'))
|
||||
const [, isOver2,, itemRef2] = useSortableWithGrip('content', {block: props.block, cords: {x: props.x}}, true, (src, dst) => moveBlock(props.card, src, dst, props.intl, 'belowRow'))
|
||||
|
||||
if (Array.isArray(props.block)) {
|
||||
return (
|
||||
<div >
|
||||
<div
|
||||
ref={itemRef}
|
||||
className={`addToRow ${isOver ? 'dragover' : ''}`}
|
||||
style={{width: '94%', height: '10px', marginLeft: '48px'}}
|
||||
/>
|
||||
<div
|
||||
style={{display: 'flex'}}
|
||||
>
|
||||
|
||||
{props.block.map((b, y) => (
|
||||
<ContentBlock
|
||||
key={b.id}
|
||||
block={b}
|
||||
card={props.card}
|
||||
readonly={props.readonly}
|
||||
width={(1 / (props.block as IContentBlock[]).length) * 100}
|
||||
onDrop={(src, dst, moveTo) => moveBlock(props.card, src, dst, props.intl, moveTo)}
|
||||
cords={{x: props.x, y}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{props.x === props.cardTree.contents.length - 1 && (
|
||||
<div
|
||||
ref={itemRef2}
|
||||
className={`addToRow ${isOver2 ? 'dragover' : ''}`}
|
||||
style={{width: '94%', height: '10px', marginLeft: '48px'}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
ref={itemRef}
|
||||
className={`addToRow ${isOver ? 'dragover' : ''}`}
|
||||
style={{width: '94%', height: '10px', marginLeft: '48px'}}
|
||||
/>
|
||||
<ContentBlock
|
||||
key={props.block.id}
|
||||
block={props.block}
|
||||
card={props.card}
|
||||
readonly={props.readonly}
|
||||
onDrop={(src, dst, moveTo) => moveBlock(props.card, src, dst, props.intl, moveTo)}
|
||||
cords={{x: props.x}}
|
||||
/>
|
||||
{props.x === props.cardTree.contents.length - 1 && (
|
||||
<div
|
||||
ref={itemRef2}
|
||||
className={`addToRow ${isOver2 ? 'dragover' : ''}`}
|
||||
style={{width: '94%', height: '10px', marginLeft: '48px'}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const CardDetailContents = React.memo((props: Props) => {
|
||||
const intl = useIntl()
|
||||
const {cardTree} = props
|
||||
@ -55,20 +181,22 @@ const CardDetailContents = React.memo((props: Props) => {
|
||||
return null
|
||||
}
|
||||
const {card} = cardTree
|
||||
|
||||
if (cardTree.contents.length > 0) {
|
||||
return (
|
||||
<div className='octo-content'>
|
||||
{cardTree.contents.map((block) => (
|
||||
<ContentBlock
|
||||
key={block.id}
|
||||
block={block}
|
||||
card={card}
|
||||
contents={cardTree.contents}
|
||||
readonly={props.readonly}
|
||||
onDrop={(src, dst) => moveBlock(card, src, dst, intl)}
|
||||
/>
|
||||
))}
|
||||
{cardTree.contents.map((block, x) =>
|
||||
(
|
||||
<ContentBlockWithDragAndDrop
|
||||
key={x}
|
||||
block={block}
|
||||
x={x}
|
||||
card={card}
|
||||
cardTree={cardTree}
|
||||
intl={intl}
|
||||
readonly={props.readonly}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
}
|
||||
> * {
|
||||
flex: 1 1 auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
> .octo-block-margin {
|
||||
flex: 0 0 auto;
|
||||
@ -23,3 +24,12 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.rowContents {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.addToRow {
|
||||
width: 10px;
|
||||
}
|
@ -5,7 +5,7 @@ import React from 'react'
|
||||
import {useIntl} from 'react-intl'
|
||||
|
||||
import {Card} from '../blocks/card'
|
||||
import {IContentBlock} from '../blocks/contentBlock'
|
||||
import {IContentBlock, IContentBlockWithCords} from '../blocks/contentBlock'
|
||||
import mutator from '../mutator'
|
||||
import {Utils} from '../utils'
|
||||
import IconButton from '../widgets/buttons/iconButton'
|
||||
@ -18,6 +18,7 @@ import GripIcon from '../widgets/icons/grip'
|
||||
import Menu from '../widgets/menu'
|
||||
import MenuWrapper from '../widgets/menuWrapper'
|
||||
import {useSortableWithGrip} from '../hooks/sortable'
|
||||
import {Position} from '../components/cardDetail/cardDetailContents'
|
||||
|
||||
import ContentElement from './content/contentElement'
|
||||
import AddContentMenuItem from './addContentMenuItem'
|
||||
@ -27,29 +28,35 @@ import './contentBlock.scss'
|
||||
type Props = {
|
||||
block: IContentBlock
|
||||
card: Card
|
||||
contents: readonly IContentBlock[]
|
||||
readonly: boolean
|
||||
onDrop: (srctBlock: IContentBlock, dstBlock: IContentBlock) => void
|
||||
onDrop: (srctBlock: IContentBlockWithCords, dstBlock: IContentBlockWithCords, position: Position) => void
|
||||
width?: number
|
||||
cords: {x: number, y?: number, z?: number}
|
||||
}
|
||||
|
||||
const ContentBlock = React.memo((props: Props): JSX.Element => {
|
||||
const {card, contents, block, readonly} = props
|
||||
const {card, block, readonly, cords} = props
|
||||
const intl = useIntl()
|
||||
const [isDragging, isOver, gripRef, itemRef] = useSortableWithGrip('content', block, true, props.onDrop)
|
||||
const [, , gripRef, itemRef] = useSortableWithGrip('content', {block, cords}, true, () => {})
|
||||
const [, isOver2,, itemRef2] = useSortableWithGrip('content', {block, cords}, true, (src, dst) => props.onDrop(src, dst, 'right'))
|
||||
const [, isOver3,, itemRef3] = useSortableWithGrip('content', {block, cords}, true, (src, dst) => props.onDrop(src, dst, 'left'))
|
||||
|
||||
const index = contents.indexOf(block)
|
||||
let className = 'ContentBlock octo-block'
|
||||
if (isOver) {
|
||||
className += ' dragover'
|
||||
}
|
||||
const index = cords.x
|
||||
const colIndex = (cords.y || cords.y === 0) && cords.y > -1 ? cords.y : -1
|
||||
const contentOrder = card.contentOrder.slice()
|
||||
|
||||
const className = 'ContentBlock octo-block'
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
style={{opacity: isDragging ? 0.5 : 1}}
|
||||
ref={itemRef}
|
||||
className='rowContents'
|
||||
style={{width: props.width + '%'}}
|
||||
>
|
||||
<div className='octo-block-margin'>
|
||||
{!props.readonly &&
|
||||
<div
|
||||
ref={itemRef}
|
||||
className={className}
|
||||
>
|
||||
<div className='octo-block-margin'>
|
||||
{!props.readonly &&
|
||||
<MenuWrapper>
|
||||
<IconButton icon={<OptionsIcon/>}/>
|
||||
<Menu>
|
||||
@ -59,18 +66,16 @@ const ContentBlock = React.memo((props: Props): JSX.Element => {
|
||||
name={intl.formatMessage({id: 'ContentBlock.moveUp', defaultMessage: 'Move up'})}
|
||||
icon={<SortUpIcon/>}
|
||||
onClick={() => {
|
||||
const contentOrder = contents.map((o) => o.id)
|
||||
Utils.arrayMove(contentOrder, index, index - 1)
|
||||
mutator.changeCardContentOrder(card, contentOrder)
|
||||
}}
|
||||
/>}
|
||||
{index < (contents.length - 1) &&
|
||||
{index < (contentOrder.length - 1) &&
|
||||
<Menu.Text
|
||||
id='moveDown'
|
||||
name={intl.formatMessage({id: 'ContentBlock.moveDown', defaultMessage: 'Move down'})}
|
||||
icon={<SortDownIcon/>}
|
||||
onClick={() => {
|
||||
const contentOrder = contents.map((o) => o.id)
|
||||
Utils.arrayMove(contentOrder, index, index + 1)
|
||||
mutator.changeCardContentOrder(card, contentOrder)
|
||||
}}
|
||||
@ -84,9 +89,8 @@ const ContentBlock = React.memo((props: Props): JSX.Element => {
|
||||
<AddContentMenuItem
|
||||
key={type}
|
||||
type={type}
|
||||
block={block}
|
||||
card={card}
|
||||
contents={contents}
|
||||
cords={cords}
|
||||
/>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
@ -96,7 +100,18 @@ const ContentBlock = React.memo((props: Props): JSX.Element => {
|
||||
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)
|
||||
|
||||
if (colIndex > -1) {
|
||||
(contentOrder[index] as string[]).splice(colIndex, 1)
|
||||
} else {
|
||||
contentOrder.splice(index, 1)
|
||||
}
|
||||
|
||||
// If only one item in the row, convert form an array item to normal item ( [item] => item )
|
||||
if (Array.isArray(contentOrder[index]) && contentOrder[index].length === 1) {
|
||||
contentOrder[index] = contentOrder[index][0]
|
||||
}
|
||||
|
||||
mutator.performAsUndoGroup(async () => {
|
||||
await mutator.deleteBlock(block, description)
|
||||
await mutator.changeCardContentOrder(card, contentOrder, description)
|
||||
@ -105,19 +120,31 @@ const ContentBlock = React.memo((props: Props): JSX.Element => {
|
||||
/>
|
||||
</Menu>
|
||||
</MenuWrapper>
|
||||
}
|
||||
{!props.readonly &&
|
||||
}
|
||||
{!props.readonly &&
|
||||
<div
|
||||
ref={gripRef}
|
||||
className='dnd-handle'
|
||||
>
|
||||
<GripIcon/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{!cords.y /* That is to say if cords.y === 0 or cords.y === undefined */ &&
|
||||
<div
|
||||
ref={gripRef}
|
||||
className='dnd-handle'
|
||||
>
|
||||
<GripIcon/>
|
||||
</div>
|
||||
ref={itemRef3}
|
||||
className={`addToRow ${isOver3 ? 'dragover' : ''}`}
|
||||
style={{flex: 'none', height: '100%'}}
|
||||
/>
|
||||
}
|
||||
<ContentElement
|
||||
block={block}
|
||||
readonly={readonly}
|
||||
/>
|
||||
</div>
|
||||
<ContentElement
|
||||
block={block}
|
||||
readonly={readonly}
|
||||
<div
|
||||
ref={itemRef2}
|
||||
className={`addToRow ${isOver2 ? 'dragover' : ''}`}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -77,7 +77,7 @@
|
||||
}
|
||||
}
|
||||
> .content.fullwidth {
|
||||
padding: 10px 0 10px 0;
|
||||
padding-left: 78px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,8 +42,19 @@ const GalleryCard = React.memo((props: Props) => {
|
||||
|
||||
const visiblePropertyTemplates = props.visiblePropertyTemplates || []
|
||||
|
||||
let images: IContentBlock[] = []
|
||||
images = cardTree.contents.filter((content) => content.type === 'image')
|
||||
let image: IContentBlock | undefined
|
||||
for (let i = 0; i < cardTree.contents.length; ++i) {
|
||||
if (Array.isArray(cardTree.contents[i])) {
|
||||
image = (cardTree.contents[i] as IContentBlock[]).find((c) => c.type === 'image')
|
||||
} else if ((cardTree.contents[i] as IContentBlock).type === 'image') {
|
||||
image = cardTree.contents[i] as IContentBlock
|
||||
}
|
||||
|
||||
if (image) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let className = props.isSelected ? 'GalleryCard selected' : 'GalleryCard'
|
||||
if (isOver) {
|
||||
className += ' dragover'
|
||||
@ -81,19 +92,31 @@ const GalleryCard = React.memo((props: Props) => {
|
||||
</MenuWrapper>
|
||||
}
|
||||
|
||||
{images?.length > 0 &&
|
||||
{image &&
|
||||
<div className='gallery-image'>
|
||||
<ImageElement block={images[0]}/>
|
||||
<ImageElement block={image}/>
|
||||
</div>}
|
||||
{images?.length === 0 &&
|
||||
{!image &&
|
||||
<div className='gallery-item'>
|
||||
{cardTree && images?.length === 0 && cardTree.contents.map((block) => (
|
||||
<ContentElement
|
||||
key={block.id}
|
||||
block={block}
|
||||
readonly={true}
|
||||
/>
|
||||
))}
|
||||
{cardTree?.contents.map((block) => {
|
||||
if (Array.isArray(block)) {
|
||||
return block.map((b) => (
|
||||
<ContentElement
|
||||
key={b.id}
|
||||
block={b}
|
||||
readonly={true}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
return (
|
||||
<ContentElement
|
||||
key={block.id}
|
||||
block={block}
|
||||
readonly={true}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>}
|
||||
{props.visibleTitle &&
|
||||
<div className='gallery-title'>
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
pre.CodeMirror-line {
|
||||
padding: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.CodeMirror,
|
||||
@ -44,6 +45,7 @@
|
||||
p {
|
||||
margin: 0;
|
||||
min-height: 32px;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,7 +171,7 @@ class Mutator {
|
||||
await this.updateBlock(newBoard, board, actionDescription)
|
||||
}
|
||||
|
||||
async changeCardContentOrder(card: Card, contentOrder: string[], description = 'reorder'): Promise<void> {
|
||||
async changeCardContentOrder(card: Card, contentOrder: Array<string | string[]>, description = 'reorder'): Promise<void> {
|
||||
const newCard = new MutableCard(card)
|
||||
newCard.contentOrder = contentOrder
|
||||
await this.updateBlock(newCard, card, description)
|
||||
|
@ -52,29 +52,6 @@ class OctoUtils {
|
||||
return displayValue
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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 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 {
|
||||
switch (block.type) {
|
||||
case 'board': { return new MutableBoard(block) }
|
||||
@ -149,7 +126,7 @@ class OctoUtils {
|
||||
// Remap card content order
|
||||
if (newBlock.type === 'card') {
|
||||
const card = newBlock as MutableCard
|
||||
card.contentOrder = card.contentOrder.map((o) => idMap[o])
|
||||
card.contentOrder = card.contentOrder.map((o) => (Array.isArray(o) ? o.map((o2) => idMap[o2]) : idMap[o]))
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -203,7 +203,6 @@ h1 {
|
||||
|
||||
.octo-content {
|
||||
width: 100%;
|
||||
margin-right: 50px;
|
||||
}
|
||||
|
||||
.octo-block {
|
||||
@ -213,9 +212,6 @@ h1 {
|
||||
|
||||
width: 100%;
|
||||
|
||||
@media not screen and (max-width: 975px) {
|
||||
padding-right: 126px;
|
||||
}
|
||||
@media screen and (max-width: 975px) {
|
||||
padding-right: 10px;
|
||||
}
|
||||
@ -233,10 +229,8 @@ h1 {
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
padding-top: 10px;
|
||||
padding-right: 10px;
|
||||
|
||||
@media not screen and (max-width: 975px) {
|
||||
width: 126px;
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
|
@ -57,8 +57,9 @@ test('CardTree', async () => {
|
||||
const image2 = TestBlockFactory.createImage(card)
|
||||
await Utils.sleep(10)
|
||||
const divider2 = TestBlockFactory.createDivider(card)
|
||||
card.contentOrder.push(...[text2.id, image2.id, divider2.id])
|
||||
|
||||
cardTree = MutableCardTree.incrementalUpdate(cardTree, [comment2, text2, image2, divider2])
|
||||
cardTree = MutableCardTree.incrementalUpdate(cardTree, [card, comment2, text2, image2, divider2])
|
||||
expect(cardTree).not.toBeUndefined()
|
||||
if (!cardTree) {
|
||||
fail('incrementalUpdate')
|
||||
@ -79,6 +80,7 @@ test('CardTree', async () => {
|
||||
fail('incrementalUpdate')
|
||||
}
|
||||
|
||||
card.contentOrder = [text.id, image.id, divider.id]
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([card, comment, text, image, divider])))
|
||||
cardTree = await MutableCardTree.sync(card.id)
|
||||
expect(cardTree).not.toBeUndefined()
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
|
||||
import {ContentBlockTypes, contentBlockTypes, IBlock} from '../blocks/block'
|
||||
import {ContentBlockTypes, contentBlockTypes, IBlock, MutableBlock} from '../blocks/block'
|
||||
import {Card, MutableCard} from '../blocks/card'
|
||||
import {CommentBlock} from '../blocks/commentBlock'
|
||||
import {IContentBlock} from '../blocks/contentBlock'
|
||||
@ -12,7 +12,7 @@ import {OctoUtils} from '../octoUtils'
|
||||
interface CardTree {
|
||||
readonly card: Card
|
||||
readonly comments: readonly CommentBlock[]
|
||||
readonly contents: readonly IContentBlock[]
|
||||
readonly contents: Readonly<Array< IContentBlock |IContentBlock[] >>
|
||||
readonly allBlocks: readonly IBlock[]
|
||||
readonly latestBlock: IBlock
|
||||
}
|
||||
@ -20,11 +20,11 @@ interface CardTree {
|
||||
class MutableCardTree implements CardTree {
|
||||
card: MutableCard
|
||||
comments: CommentBlock[] = []
|
||||
contents: IContentBlock[] = []
|
||||
contents: (IContentBlock[] | IContentBlock)[] = []
|
||||
latestBlock: IBlock
|
||||
|
||||
get allBlocks(): IBlock[] {
|
||||
return [this.card, ...this.comments, ...this.contents]
|
||||
return [this.card, ...this.comments, ...this.contents.flat()]
|
||||
}
|
||||
|
||||
constructor(card: MutableCard) {
|
||||
@ -62,7 +62,14 @@ class MutableCardTree implements CardTree {
|
||||
sort((a, b) => a.createAt - b.createAt) as CommentBlock[]
|
||||
|
||||
const contentBlocks = blocks.filter((block) => contentBlockTypes.includes(block.type as ContentBlockTypes)) as IContentBlock[]
|
||||
cardTree.contents = OctoUtils.getBlockOrder(card.contentOrder, contentBlocks)
|
||||
|
||||
cardTree.contents = card.contentOrder.map((contentIds) => {
|
||||
if (Array.isArray(contentIds)) {
|
||||
return contentIds.map((contentId) => contentBlocks.find((content) => content.id === contentId)).filter((content): content is IContentBlock => Boolean(content))
|
||||
}
|
||||
|
||||
return contentBlocks.find((content) => content.id === contentIds) || new MutableBlock()
|
||||
})
|
||||
|
||||
cardTree.latestBlock = MutableCardTree.getMostRecentBlock(cardTree)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user