Skip to content

Commit

Permalink
[field] Simplify rendering of PT and display more diffs
Browse files Browse the repository at this point in the history
  • Loading branch information
skogsmaskin authored and rexxars committed Oct 6, 2020
1 parent 0babe58 commit 5ea4be4
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 125 deletions.
21 changes: 16 additions & 5 deletions packages/@sanity/field/src/diff/components/portableText/PTDiff.css
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
@import 'part:@sanity/base/theme/variables-style';

.root {
}

.spanDiff {
}

.inlineObjectDiff {
border: 1px solid #999;
padding: 3px;
@nest & .removed {
opacity: 0.5;
}
}

.removed {
text-decoration: line-through;
opacity: 0.5;
}

.summary {
font-size: var(--font-size-xsmall);
color: var(--text-muted);
}

.summary li {
}

.inlineObjectDiff {
border: 1px solid #999;
padding: 3px;
}
128 changes: 25 additions & 103 deletions packages/@sanity/field/src/diff/components/portableText/PTDiff.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import React from 'react'
import PortableText from '@sanity/block-content-to-react'
import {DiffComponent, ObjectDiff, ObjectSchemaType} from '../../index'
import {ChangeList} from '../../changes'
// import PortableText from '@sanity/block-content-to-react'
import classNames from 'classnames'
import {DiffComponent, ObjectDiff, ObjectSchemaType, useDiffAnnotationColor} from '../../index'

import Blockquote from './previews/Blockquote'
import Decorator from './previews/Decorator'
import Header from './previews/Header'
import Paragraph from './previews/Paragraph'
import {PortableText} from './PortableText'

import {createChildMap, isHeader} from './helpers'
import {createChildMap} from './helpers'

import styles from './PTDiff.css'
import {ChildMap, PortableTextBlock, PortableTextChild} from './types'

export const PTDiff: DiffComponent<ObjectDiff> = function PTDiff({
diff,
Expand All @@ -20,102 +16,28 @@ export const PTDiff: DiffComponent<ObjectDiff> = function PTDiff({
diff: ObjectDiff
schemaType: ObjectSchemaType
}) {
const block = (diff.toValue ? diff.toValue : diff.fromValue) as PortableTextBlock
const blocks = [block] as PortableTextBlock[]
const childMap = createChildMap(block, diff)
const serializers = createSerializers(schemaType, childMap)
const isRemoved = diff.action === 'removed'
const isAdded = diff.action === 'added'
const childMap = createChildMap(diff, schemaType)
return (
<div className={styles.root}>
<PortableText blocks={blocks} serializers={serializers} />
<>
{/* Preview */}
<div className={styles.root}>
<PortableText blockDiff={diff} childMap={childMap} />
</div>

{/* Summary */}
<ul className={styles.summary}>
{block.children.map(child => {
return childMap[child._key].summary.map((line, i) => (
<li key={`summary-${child._key.concat(i.toString())}`}>{line}</li>
))
})}
{isRemoved && <li>Removed portable text block</li>}
{isAdded && <li>Added portable text block</li>}
{Object.keys(childMap)
.map(key => childMap[key])
.map(mapEntry => {
return mapEntry.summary.map((line, i) => (
<li key={`summary-${mapEntry.child._key.concat(i.toString())}`}>{line}</li>
))
})}
</ul>
</div>
</>
)
}

function createSerializers(schemaType: ObjectSchemaType, childMap: ChildMap) {
const renderDecorator = ({mark, children}: {mark: string; children: React.ReactNode}) => {
return <Decorator mark={mark}>{children}</Decorator>
}
const renderBlock = ({node, children}: {node: PortableTextBlock; children: React.ReactNode}) => {
let returned: React.ReactNode = children
if (node.style === 'blockquote') {
returned = <Blockquote>{returned}</Blockquote>
} else if (node.style && isHeader(node)) {
returned = <Header style={node.style}>{returned}</Header>
} else {
returned = <Paragraph>{returned}</Paragraph>
}
return returned
}
const renderText = (text: {children: string}) => {
// With '@sanity/block-content-to-react', spans without marks doesn't run through the renderSpan function.
// They are sent directly to the 'text' serializer. This is a hack to render those with annotations from childMap
// The _key for the child is not known at this point.

// Find child that has no marks, and a text similar to what is in the childMap.
const fromMap = Object.keys(childMap)
.map(key => childMap[key])
.filter(child => (child.node.marks || []).length === 0)
.find(child => child.node.text === text.children)
if (fromMap && fromMap.annotation) {
return <span className={styles.spanDiff}>{fromMap.annotation}</span>
}
return text.children
}
const renderSpan = (props: {node: PortableTextChild}): React.ReactNode => {
const fromMap = childMap[props.node._key]
if (fromMap && fromMap.annotation) {
const annotatedProps = {
...props,
node: {...props.node, children: fromMap.annotation}
}
return (
<span className={styles.spanDiff}>
{PortableText.defaultSerializers.span(annotatedProps)}
</span>
)
}
return PortableText.defaultSerializers.span(props)
}
const renderInlineObject = (props: {node: PortableTextChild}): React.ReactNode => {
let returned = <span className={styles.inlineObjectDiff}>{props.node._type}</span>
return returned
// TODO: restore this when we have the inline object schema type
// const childMapEntry = childMap[props.node._key]
// if (childMapEntry && childMapEntry.diffs.length > 0) {
// returned = null
// childMapEntry.diffs.forEach(iDiff => {
// returned = <ChangeList diff={iDiff} schemaType={schemaType} />
// })
// }
// return returned
}

const otherTypes = {}
const inlineObjects = Object.keys(childMap)
.map(key => childMap[key])
.map(child => {
const span = typeof child === 'object' && (child.node as PortableTextChild)
const type = typeof child === 'object' && (child.node._type as string)
const isSpan = typeof span._type === 'string' && span._type === 'span'
if (!isSpan) {
otherTypes[type] = renderInlineObject
}
})
// TODO: create serializers according to schemaType (marks etc)
return {
marks: {strong: renderDecorator, italic: renderDecorator},
span: renderSpan,
text: renderText,
types: {
block: renderBlock,
...otherTypes
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react'
import {PortableTextBlock, PortableTextChild, ChildMap} from './types'
import {isHeader, somethingIsRemoved} from './helpers'

import Decorator from './previews/Decorator'
import InlineObject from './previews/InlineObject'
import Blockquote from './previews/Blockquote'
import Header from './previews/Header'
import Paragraph from './previews/Paragraph'

import styles from './PTDiff.css'
import {ArrayDiff, ObjectDiff} from '../../index'

type Props = {
blockDiff: ObjectDiff
childMap: ChildMap
}

export const PortableText = (props: Props): JSX.Element => {
const {blockDiff, childMap} = props

const renderBlock = ({
block,
children
}: {
block: PortableTextBlock
children: React.ReactNode
}): JSX.Element => {
let returned: React.ReactNode = children
if (block.style === 'blockquote') {
returned = <Blockquote>{returned}</Blockquote>
} else if (block.style && isHeader(block)) {
returned = <Header style={block.style}>{returned}</Header>
} else {
returned = <Paragraph>{returned}</Paragraph>
}
return <>{returned}</>
}
const renderInlineObject = (props: {child: PortableTextChild}): React.ReactNode => {
const {child} = props
return <InlineObject node={child} />
}

const invalidInlineObjectType = (props: any) => {
return <span className={styles.inlineObjectDiff}>Invalid type</span>
}

const otherTypes = {}
Object.keys(childMap)
.map(key => childMap[key])
.forEach(mapEntry => {
const {child} = mapEntry
const type = typeof child === 'object' && (child._type as string)
const isSpan = child && typeof child._type === 'string' && child._type === 'span'
if (!isSpan && type) {
otherTypes[type] = renderInlineObject
} else {
// This should not happen. But have a fallback for missing types anyway.
// 'undefined' key is set when building the childMap (helpers) when there is no schema found for this object
otherTypes['undefined'] = props => invalidInlineObjectType(props)
}
})

const renderChild = (child: PortableTextChild) => {
const fromMap = childMap[child._key]
// Render span or inline object?
const inlineObject = otherTypes[child._type]
const renderInlineObject = inlineObject && inlineObject(child)
const renderSpanOrInline = renderInlineObject ? renderInlineObject : () => child.text
let returned: React.ReactNode =
fromMap && fromMap.annotation ? fromMap.annotation : renderSpanOrInline({child})
// Render decorators
!inlineObject &&
(child.marks || []).map(mark => {
returned = <Decorator mark={mark}>{returned}</Decorator>
})
return returned
}

// Do the final render
let block = blockDiff.toValue
if (somethingIsRemoved(blockDiff)) {
block = blockDiff.fromValue
}
return renderBlock({
block: block as PortableTextBlock,
children: block.children.map(child => renderChild(child))
})
}

0 comments on commit 5ea4be4

Please sign in to comment.