Move content registration to elements

This commit is contained in:
Chen-I Lim
2021-03-10 10:53:37 -08:00
parent cfe763ff47
commit 9f87b680c2
6 changed files with 141 additions and 99 deletions
@@ -5,8 +5,15 @@ import React from 'react'
import {injectIntl, IntlShape} from 'react-intl'
import {IContentBlock} from '../../blocks/contentBlock'
import {Utils} from '../../utils'
import contentRegistry from './contentRegistry'
import {contentRegistry} from './contentRegistry'
// Need to require here to prevent webpack from tree-shaking these away
// TODO: Update webpack to avoid this
require('./textElement')
require('./imageElement')
require('./dividerElement')
type Props = {
block: IContentBlock
@@ -16,9 +23,15 @@ type Props = {
class ContentElement extends React.PureComponent<Props> {
public render(): JSX.Element | null {
const {block, readonly} = this.props
const {block, intl, readonly} = this.props
return contentRegistry.createComponent(block, readonly) || null
const handler = contentRegistry.getHandler(block.type)
if (!handler) {
Utils.logError(`ContentElement, unknown content type: ${block.type}`)
return null
}
return handler.createComponent(block, intl, readonly)
}
}
@@ -1,83 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable react/require-optimization */
import React from 'react'
import {IntlShape} from 'react-intl'
import {BlockTypes} from '../../blocks/block'
import {IContentBlock, MutableContentBlock} from '../../blocks/contentBlock'
import {MutableDividerBlock} from '../../blocks/dividerBlock'
import {MutableImageBlock} from '../../blocks/imageBlock'
import {MutableTextBlock} from '../../blocks/textBlock'
import {Utils} from '../../utils'
import DividerIcon from '../../widgets/icons/divider'
import ImageIcon from '../../widgets/icons/image'
import TextIcon from '../../widgets/icons/text'
import DividerElement from './dividerElement'
import ImageElement from './imageElement'
import TextElement from './textElement'
type RegistryEntry = {
type ContentHandler = {
type: BlockTypes,
createBlock: () => MutableContentBlock,
getDisplayText: (intl: IntlShape) => string,
getIcon: () => JSX.Element,
createComponent: (block: IContentBlock, readonly: boolean) => JSX.Element,
createBlock: () => MutableContentBlock,
createComponent: (block: IContentBlock, intl: IntlShape, readonly: boolean) => JSX.Element,
}
class ContentRegistry {
private registry: Map<BlockTypes, RegistryEntry> = new Map()
private registry: Map<BlockTypes, ContentHandler> = new Map()
get contentTypes(): BlockTypes[] {
return [...this.registry.keys()]
}
constructor() {
this.registerContentType(
{
type: 'text',
createBlock: () => {
return new MutableTextBlock()
},
getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.text', defaultMessage: 'text'}),
getIcon: () => <TextIcon/>,
createComponent: (block, readonly) => (
<TextElement
block={block}
readonly={readonly}
/>
),
},
)
this.registerContentType(
{
type: 'image',
createBlock: () => {
return new MutableImageBlock()
},
getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.image', defaultMessage: 'image'}),
getIcon: () => <ImageIcon/>,
createComponent: (block) => <ImageElement block={block}/>,
},
)
this.registerContentType(
{
type: 'divider',
createBlock: () => {
return new MutableDividerBlock()
},
getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.divider', defaultMessage: 'divider'}),
getIcon: () => <DividerIcon/>,
createComponent: () => <DividerElement/>,
},
)
}
private registerContentType(entry: RegistryEntry) {
registerContentType(entry: ContentHandler) {
this.registry.set(entry.type, entry)
}
@@ -85,32 +29,37 @@ class ContentRegistry {
return this.registry.has(type)
}
createBlock(type: BlockTypes): MutableContentBlock | undefined {
const entry = this.registry.get(type)
return entry?.createBlock()
getHandler(type: BlockTypes): ContentHandler | undefined {
return this.registry.get(type)
}
createComponent(block: IContentBlock, readonly: boolean): JSX.Element | undefined {
const entry = this.registry.get(block.type)
return entry?.createComponent(block, readonly)
}
// createBlock(type: BlockTypes): MutableContentBlock | undefined {
// const entry = this.registry.get(type)
// return entry?.createBlock()
// }
typeDisplayText(intl: IntlShape, type: BlockTypes): string {
const entry = this.registry.get(type)
if (!entry) {
Utils.logError(`Unknown type: ${type}`)
return type
}
// createComponent(block: IContentBlock, intl: IntlShape, readonly: boolean): JSX.Element | undefined {
// const entry = this.registry.get(block.type)
// return entry?.createComponent(block, intl, readonly)
// }
return entry.getDisplayText(intl)
}
// typeDisplayText(intl: IntlShape, type: BlockTypes): string {
// const entry = this.registry.get(type)
// if (!entry) {
// Utils.logError(`Unknown type: ${type}`)
// return type
// }
getIcon(type: BlockTypes): JSX.Element | undefined {
const entry = this.registry.get(type)
return entry?.getIcon()
}
// return entry.getDisplayText(intl)
// }
// getIcon(type: BlockTypes): JSX.Element | undefined {
// const entry = this.registry.get(type)
// return entry?.getIcon()
// }
}
const contentRegistry = new ContentRegistry()
export default contentRegistry
// export type {ContentHandler}
export {contentRegistry}
@@ -1,10 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {FC} from 'react'
import React from 'react'
import {MutableDividerBlock} from '../../blocks/dividerBlock'
import DividerIcon from '../../widgets/icons/divider'
import {contentRegistry} from './contentRegistry'
import './dividerElement.scss'
const DividerElement: FC = (): JSX.Element => (
<div className='DividerElement'/>
)
class DividerElement extends React.PureComponent {
render(): JSX.Element {
return <div className='DividerElement'/>
}
}
contentRegistry.registerContentType({
type: 'divider',
getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.divider', defaultMessage: 'divider'}),
getIcon: () => <DividerIcon/>,
createBlock: () => {
return new MutableDividerBlock()
},
createComponent: () => <DividerElement/>,
})
export default DividerElement
+22 -1
View File
@@ -3,8 +3,12 @@
import React from 'react'
import {injectIntl, IntlShape} from 'react-intl'
import {IContentBlock} from '../../blocks/contentBlock'
import {IContentBlock, MutableContentBlock} from '../../blocks/contentBlock'
import {MutableImageBlock} from '../../blocks/imageBlock'
import octoClient from '../../octoClient'
import ImageIcon from '../../widgets/icons/image'
import {contentRegistry} from './contentRegistry'
type Props = {
block: IContentBlock
@@ -46,4 +50,21 @@ class ImageElement extends React.PureComponent<Props> {
}
}
contentRegistry.registerContentType({
type: 'image',
getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.image', defaultMessage: 'image'}),
getIcon: () => <ImageIcon/>,
createBlock: () => {
return new MutableImageBlock()
},
createComponent: (block, intl) => {
return (
<ImageElement
block={block}
intl={intl}
/>
)
},
})
export default injectIntl(ImageElement)
+38 -2
View File
@@ -3,10 +3,14 @@
import React from 'react'
import {injectIntl, IntlShape} from 'react-intl'
import {IContentBlock} from '../../blocks/contentBlock'
import {IContentBlock, MutableContentBlock} from '../../blocks/contentBlock'
import {MutableTextBlock} from '../../blocks/textBlock'
import mutator from '../../mutator'
import TextIcon from '../../widgets/icons/text'
import {MarkdownEditor} from '../markdownEditor'
import {contentRegistry} from './contentRegistry'
type Props = {
block: IContentBlock
readonly: boolean
@@ -14,7 +18,21 @@ type Props = {
}
class TextElement extends React.PureComponent<Props> {
public render(): JSX.Element {
readonly type = 'text'
createBlock(): MutableContentBlock {
return new MutableTextBlock()
}
getDisplayText(intl: IntlShape): string {
return intl.formatMessage({id: 'ContentBlock.text', defaultMessage: 'text'})
}
getIcon(): JSX.Element {
return <TextIcon/>
}
render(): JSX.Element {
const {intl, block, readonly} = this.props
return (
@@ -30,4 +48,22 @@ class TextElement extends React.PureComponent<Props> {
}
}
contentRegistry.registerContentType({
type: 'text',
getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.text', defaultMessage: 'text'}),
getIcon: () => <TextIcon/>,
createBlock: () => {
return new MutableTextBlock()
},
createComponent: (block, intl, readonly) => {
return (
<TextElement
block={block}
intl={intl}
readonly={readonly}
/>
)
},
})
export default injectIntl(TextElement)
+14 -8
View File
@@ -19,7 +19,7 @@ import Menu from '../widgets/menu'
import MenuWrapper from '../widgets/menuWrapper'
import ContentElement from './content/contentElement'
import contentRegistry from './content/contentRegistry'
import {contentRegistry} from './content/contentRegistry'
import './contentBlock.scss'
type Props = {
@@ -100,17 +100,23 @@ class ContentBlock extends React.PureComponent<Props> {
const {intl, card, contents, block} = this.props
const index = contents.indexOf(block)
const handler = contentRegistry.getHandler(type)
if (!handler) {
Utils.logError(`addContentMenu, unknown content type: ${type}`)
return <></>
}
switch (type) {
case 'image': return (
<Menu.Text
ref={type}
id={type}
name={contentRegistry.typeDisplayText(intl, type)}
icon={contentRegistry.getIcon(type)}
name={handler.getDisplayText(intl)}
icon={handler.getIcon()}
onClick={() => {
Utils.selectLocalFile((file) => {
mutator.performAsUndoGroup(async () => {
const description = intl.formatMessage({id: 'ContentBlock.addElement', defaultMessage: 'add {type}'}, {type: contentRegistry.typeDisplayText(intl, type)})
const description = intl.formatMessage({id: 'ContentBlock.addElement', defaultMessage: 'add {type}'}, {type: handler.getDisplayText(intl)})
const newBlock = await mutator.createImageBlock(card, file, description)
if (newBlock) {
const contentOrder = contents.map((o) => o.id)
@@ -127,17 +133,17 @@ class ContentBlock extends React.PureComponent<Props> {
<Menu.Text
ref={type}
id={type}
name={contentRegistry.typeDisplayText(intl, type)}
icon={contentRegistry.getIcon(type)}
name={handler.getDisplayText(intl)}
icon={handler.getIcon()}
onClick={() => {
const newBlock = contentRegistry.createBlock(type)!
const newBlock = handler.createBlock()!
newBlock.parentId = card.id
newBlock.rootId = card.rootId
const contentOrder = contents.map((o) => o.id)
contentOrder.splice(index, 0, newBlock.id)
mutator.performAsUndoGroup(async () => {
const description = intl.formatMessage({id: 'ContentBlock.addElement', defaultMessage: 'add {type}'}, {type: contentRegistry.typeDisplayText(intl, type)})
const description = intl.formatMessage({id: 'ContentBlock.addElement', defaultMessage: 'add {type}'}, {type: handler.getDisplayText(intl)})
await mutator.insertBlock(newBlock, description)
await mutator.changeCardContentOrder(card, contentOrder, description)
})