@portabletext/markdown 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,124 +0,0 @@
1
- [
2
- "h1:Markdown to Portable Text: A Complete Guide",
3
- "Converting markdown to Portable Text is a ,powerful, way to bridge the gap between ,simple text formatting, and ,structured content,.",
4
- "h2:Why Portable Text?",
5
- "Portable Text is a ,specification, for ,rich text, that is:",
6
- ">-:Platform ,agnostic",
7
- ">-:Structured, and ,queryable",
8
- ">-:Designed for ,modern content workflows",
9
- "Visit ,https://portabletext.org, for more information, or check out the ,official documentation,.",
10
- "{horizontal-rule}",
11
- "h2:Supported Features",
12
- "h3:Text Formatting",
13
- "You can use ,bold text,, ,italic text,, ,inline code,, and even ,strikethrough, text. The parser handles ,nested formatting, gracefully, including ,bold and italic, combined.",
14
- "Here's some ,code with **bold inside**, and ,text with ,code inside, to test edge cases.",
15
- "h3:Links and Images",
16
- "Reference-style links work too! Check out this ,example link, that uses references defined elsewhere.",
17
- "{image}",
18
- "Here's an inline image in text: ,{image}, followed by more text.",
19
- "h3:Code Blocks",
20
- "Fenced code blocks with syntax highlighting:",
21
- "{code}",
22
- "Indented code blocks also work:",
23
- "{code}",
24
- "h3:Blockquotes",
25
- "q:Markdown is a lightweight markup language for creating formatted text.",
26
- "q:It was created by John Gruber in 2004.",
27
- "Nested blockquotes are supported:",
28
- "q:This is the outer quote",
29
- "q:And this is nested deeper",
30
- "q:With multiple paragraphs",
31
- "h3:Lists",
32
- "h4:Unordered Lists",
33
- ">-:Bold item, in a list",
34
- ">-:Italic item, with ,a link",
35
- ">-:Item with ,inline code",
36
- ">>-:Nested item one",
37
- ">>-:Nested item two",
38
- ">>>-:Even deeper nesting",
39
- ">-:Back to top level",
40
- "h4:Ordered Lists",
41
- ">#:First step: Parse the markdown",
42
- ">#:Second step: Generate tokens",
43
- ">#:Third step: Transform to Portable Text",
44
- ">>#:Map block types",
45
- ">>#:Handle inline formatting",
46
- ">>#:Preserve structure",
47
- "h4:Mixed Lists",
48
- ">#:Ordered parent",
49
- ">>-:Unordered child",
50
- ">>-:Another unordered",
51
- ">>>#:Back to ordered",
52
- ">>>#:Still ordered",
53
- ">#:Continue ordered parent",
54
- "h3:Tables",
55
- "Here's a comparison of different content formats:",
56
- "{table}",
57
- "Feature support matrix:",
58
- "{table}",
59
- "h3:Horizontal Rules",
60
- "You can separate sections with horizontal rules:",
61
- "{horizontal-rule}",
62
- "They create visual breaks in the content.",
63
- "{horizontal-rule}",
64
- "h3:All Heading Levels",
65
- "h1:Heading 1",
66
- "h2:Heading 2",
67
- "h3:Heading 3",
68
- "h4:Heading 4",
69
- "h5:Heading 5",
70
- "h6:Heading 6",
71
- "h2:Advanced Examples",
72
- "h3:Overlapping Formatting",
73
- "This paragraph contains ,bold with ,italic inside, and ,italic with ,bold inside, to test proper nesting.",
74
- "You can also have ,bold with ,code, and ,italic with ,code, and even ,code with **bold** and _italic_, (though the formatting inside code should be preserved as-is).",
75
- "h3:Links with Formatting",
76
- "Here's a ,bold link, and an ,italic link, and even a ,code link,.",
77
- "h3:Complex List Items",
78
- ">-:Item with ,bold,, ,italic,, and ,code",
79
- ">-:Item with a ,link to ,bold, content",
80
- ">-:Item with an inline image ,{image}, in the middle",
81
- ">>-:Nested ,bold item, with ,italic, and ,code",
82
- ">>-:Another nested item",
83
- "h3:Line Breaks",
84
- "This is a line with a hard break\nthat continues on the next line but stays in the same paragraph.",
85
- "Another paragraph with a break\nand more content.",
86
- "h3:Autolinks",
87
- "Check out these autolinks: ,https://example.com, and ,mailto:hello@example.com, for quick linking.",
88
- "h3:HTML Passthrough",
89
- "{html}",
90
- "Inline HTML like highlighted text can be handled too.",
91
- "h3:Reference Links",
92
- "This is ,reference link one, and this is ,reference link two,.",
93
- "You can also use ,implicit references, by just using the text as the reference.",
94
- "h2:Implementation Notes",
95
- "The transformation process involves several key steps:",
96
- ">#:Lexical Analysis,: Parse markdown into tokens",
97
- ">#:Syntax Tree Building,: Construct an AST from tokens",
98
- ">#:Transformation,: Map markdown AST to Portable Text structure",
99
- ">#:Validation,: Ensure output conforms to schema",
100
- "h3:Edge Cases",
101
- "h4:Empty Blocks",
102
- "Sometimes you have empty paragraphs:",
103
- "Or consecutive horizontal rules:",
104
- "{horizontal-rule}",
105
- "{horizontal-rule}",
106
- "h4:Special Characters",
107
- "Text with special characters like ,&,, ,<,, and ,>, should be handled correctly.",
108
- "h4:URLs in Text",
109
- "Raw URLs like ,https://example.com, (without angle brackets) may or may not become links depending on the parser configuration.",
110
- "h2:Conclusion",
111
- "This document demonstrates the ,comprehensive, support for ,markdown features, in the ,markdown-to-portable-text, converter. With proper handling of:",
112
- ">-:All basic formatting",
113
- ">-:Complex nesting",
114
- ">-:Various link types",
115
- ">-:Images (block and inline)",
116
- ">-:Code blocks",
117
- ">-:Lists of all types",
118
- ">-:Tables",
119
- ">-:And much more!",
120
- "The result is ,robust, reliable, content transformation that preserves both structure and semantics.",
121
- "{horizontal-rule}",
122
- "Happy converting!, 🎉",
123
- "Last updated: 2025"
124
- ]
@@ -1,87 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import type {BlockObjectDefinition} from '@portabletext/schema'
4
- import {createTestKeyGenerator, getTersePt} from '@portabletext/test'
5
- import {describe, expect, test} from 'vitest'
6
- import {defaultSchema} from './default-schema'
7
- import {portableTextToMarkdown} from './from-portable-text/portable-text-to-markdown'
8
- import {
9
- DefaultCodeBlockRenderer,
10
- DefaultTableRenderer,
11
- } from './from-portable-text/renderers/type'
12
- import {markdownToPortableText} from './to-portable-text/markdown-to-portable-text'
13
- import {buildObjectMatcher} from './to-portable-text/matchers'
14
-
15
- const exampleDocumentMarkdown = fs.readFileSync(
16
- path.resolve(__dirname, 'example-document.md'),
17
- 'utf-8',
18
- )
19
- const exampleDocumentMarkdownOut = fs
20
- .readFileSync(path.resolve(__dirname, 'example-document.out.md'), 'utf-8')
21
- // Account for hard break spaces that may be stripped by editors/tools
22
- .replace('hard break\nthat continues', 'hard break \nthat continues')
23
- .replace('with a break\nand more', 'with a break \nand more')
24
- const exampleDocumentTersePt = JSON.parse(
25
- fs.readFileSync(
26
- path.resolve(__dirname, 'example-document.terse-pt.json'),
27
- 'utf-8',
28
- ),
29
- )
30
-
31
- const tableObjectDefinition = {
32
- name: 'table',
33
- fields: [
34
- {name: 'headerRows', type: 'number'},
35
- {name: 'rows', type: 'array'},
36
- ],
37
- } as const satisfies BlockObjectDefinition
38
-
39
- const tableObjectMatcher = buildObjectMatcher(tableObjectDefinition)
40
-
41
- describe('example document', () => {
42
- test('markdown to portable text', () => {
43
- const keyGenerator = createTestKeyGenerator()
44
- const blocks = markdownToPortableText(exampleDocumentMarkdown, {
45
- keyGenerator,
46
- schema: {
47
- ...defaultSchema,
48
- blockObjects: [...defaultSchema.blockObjects, tableObjectDefinition],
49
- },
50
- types: {
51
- table: tableObjectMatcher,
52
- },
53
- })
54
- const tersePt = getTersePt({schema: defaultSchema, value: blocks})
55
-
56
- expect(tersePt).toEqual(exampleDocumentTersePt)
57
- })
58
-
59
- test('portable text to markdown', () => {
60
- const keyGenerator = createTestKeyGenerator()
61
- const blocks = markdownToPortableText(exampleDocumentMarkdown, {
62
- keyGenerator,
63
- schema: {
64
- ...defaultSchema,
65
- blockObjects: [...defaultSchema.blockObjects, tableObjectDefinition],
66
- },
67
- types: {
68
- table: tableObjectMatcher,
69
- },
70
- html: {
71
- inline: 'text',
72
- },
73
- })
74
- const markdown = portableTextToMarkdown(blocks, {
75
- types: {
76
- 'horizontal-rule': () => '---',
77
- 'table': DefaultTableRenderer,
78
- 'code': DefaultCodeBlockRenderer,
79
- 'html': ({value}) => value.html || '',
80
- 'image': ({value}) =>
81
- `![${value.alt}](${value.src}${value.title ? ` "${value.title}"` : ''})`,
82
- },
83
- })
84
-
85
- expect(`${markdown}\n`).toBe(exampleDocumentMarkdownOut)
86
- })
87
- })
@@ -1,133 +0,0 @@
1
- import {
2
- compileSchema,
3
- defineSchema,
4
- isTextBlock,
5
- type PortableTextBlock,
6
- } from '@portabletext/schema'
7
- import type {ArbitraryTypedObject, TypedObject} from '@portabletext/types'
8
- import {defaultKeyGenerator} from '../key-generator'
9
-
10
- const schema = compileSchema(defineSchema({}))
11
-
12
- /**
13
- * Builds a map of list item `_key`s to their index.
14
- *
15
- * Mutates the blocks in place by adding a `_key` if necessary.
16
- */
17
- export function buildListIndexMap<
18
- Block extends TypedObject = PortableTextBlock | ArbitraryTypedObject,
19
- >(blocks: Array<Block>): Map<string, number> {
20
- const levelIndexMaps = new Map<string, Map<number, number>>()
21
- const listIndexMap = new Map<string, number>()
22
-
23
- let previousListItem:
24
- | {
25
- listItem: string
26
- level: number
27
- }
28
- | undefined
29
-
30
- for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
31
- const block = blocks.at(blockIndex)
32
-
33
- if (block === undefined) {
34
- continue
35
- }
36
-
37
- if (!block._key) {
38
- block._key = defaultKeyGenerator()
39
- }
40
-
41
- // Clear the state if we encounter a non-text block
42
- if (!isTextBlock({schema}, block)) {
43
- levelIndexMaps.clear()
44
- previousListItem = undefined
45
-
46
- continue
47
- }
48
-
49
- // Clear the state if we encounter a non-list text block
50
- if (block.listItem === undefined || block.level === undefined) {
51
- levelIndexMaps.clear()
52
- previousListItem = undefined
53
-
54
- continue
55
- }
56
-
57
- // If we encounter a new list item, we set the initial index to 1 for the
58
- // list type on that level.
59
- if (!previousListItem) {
60
- const listIndex = 1
61
- const levelIndexMap =
62
- levelIndexMaps.get(block.listItem) ?? new Map<number, number>()
63
- levelIndexMap.set(block.level, listIndex)
64
- levelIndexMaps.set(block.listItem, levelIndexMap)
65
-
66
- listIndexMap.set(block._key, listIndex)
67
-
68
- previousListItem = {
69
- listItem: block.listItem,
70
- level: block.level,
71
- }
72
-
73
- continue
74
- }
75
-
76
- // If the previous list item is of the same type but on a lower level, we
77
- // need to reset the level index map for that type.
78
- if (
79
- previousListItem.listItem === block.listItem &&
80
- previousListItem.level < block.level
81
- ) {
82
- const listIndex = 1
83
- const levelIndexMap =
84
- levelIndexMaps.get(block.listItem) ?? new Map<number, number>()
85
- levelIndexMap.set(block.level, listIndex)
86
- levelIndexMaps.set(block.listItem, levelIndexMap)
87
-
88
- listIndexMap.set(block._key, listIndex)
89
-
90
- previousListItem = {
91
- listItem: block.listItem,
92
- level: block.level,
93
- }
94
-
95
- continue
96
- }
97
-
98
- // Reset other list types at current level and deeper
99
- levelIndexMaps.forEach((levelIndexMap, listItem) => {
100
- if (listItem === block.listItem) {
101
- return
102
- }
103
-
104
- // Reset all levels that are >= current level
105
- const levelsToDelete: number[] = []
106
-
107
- levelIndexMap.forEach((_, level) => {
108
- if (level >= block.level!) {
109
- levelsToDelete.push(level)
110
- }
111
- })
112
-
113
- levelsToDelete.forEach((level) => {
114
- levelIndexMap.delete(level)
115
- })
116
- })
117
-
118
- const levelIndexMap =
119
- levelIndexMaps.get(block.listItem) ?? new Map<number, number>()
120
- const levelCounter = levelIndexMap.get(block.level) ?? 0
121
- levelIndexMap.set(block.level, levelCounter + 1)
122
- levelIndexMaps.set(block.listItem, levelIndexMap)
123
-
124
- listIndexMap.set(block._key, levelCounter + 1)
125
-
126
- previousListItem = {
127
- listItem: block.listItem,
128
- level: block.level,
129
- }
130
- }
131
-
132
- return listIndexMap
133
- }
@@ -1,135 +0,0 @@
1
- import type {
2
- ArbitraryTypedObject,
3
- PortableTextBlock,
4
- TypedObject,
5
- } from '@portabletext/types'
6
- import {buildListIndexMap} from './build-list-index-map'
7
- import {createRenderNode} from './render-node'
8
- import {
9
- DefaultBlockSpacingRenderer,
10
- type BlockSpacingRenderer,
11
- } from './renderers/block-spacing'
12
- import {DefaultHardBreakRenderer} from './renderers/hard-break'
13
- import {
14
- DefaultListItemRenderer,
15
- DefaultUnknownListItemRenderer,
16
- } from './renderers/list-item'
17
- import {
18
- DefaultCodeRenderer,
19
- DefaultEmRenderer,
20
- DefaultLinkRenderer,
21
- DefaultStrikeThroughRenderer,
22
- DefaultStrongRenderer,
23
- DefaultUnderlineRenderer,
24
- DefaultUnknownMarkRenderer,
25
- } from './renderers/marks'
26
- import {
27
- DefaultBlockquoteRenderer,
28
- DefaultH1Renderer,
29
- DefaultH2Renderer,
30
- DefaultH3Renderer,
31
- DefaultH4Renderer,
32
- DefaultH5Renderer,
33
- DefaultH6Renderer,
34
- DefaultNormalRenderer,
35
- DefaultUnknownStyleRenderer,
36
- } from './renderers/style'
37
- import {DefaultUnknownTypeRenderer} from './renderers/type'
38
- import type {PortableTextRenderers} from './types'
39
-
40
- const defaultRenderers: PortableTextRenderers = {
41
- types: {},
42
-
43
- block: {
44
- normal: DefaultNormalRenderer,
45
- blockquote: DefaultBlockquoteRenderer,
46
- h1: DefaultH1Renderer,
47
- h2: DefaultH2Renderer,
48
- h3: DefaultH3Renderer,
49
- h4: DefaultH4Renderer,
50
- h5: DefaultH5Renderer,
51
- h6: DefaultH6Renderer,
52
- },
53
- marks: {
54
- 'em': DefaultEmRenderer,
55
- 'strong': DefaultStrongRenderer,
56
- 'code': DefaultCodeRenderer,
57
- 'underline': DefaultUnderlineRenderer,
58
- 'strike-through': DefaultStrikeThroughRenderer,
59
- 'link': DefaultLinkRenderer,
60
- },
61
- listItem: DefaultListItemRenderer,
62
- hardBreak: DefaultHardBreakRenderer,
63
-
64
- unknownType: DefaultUnknownTypeRenderer,
65
- unknownMark: DefaultUnknownMarkRenderer,
66
- unknownListItem: DefaultUnknownListItemRenderer,
67
- unknownBlockStyle: DefaultUnknownStyleRenderer,
68
- }
69
-
70
- type Options = Partial<PortableTextRenderers> & {
71
- blockSpacing?: BlockSpacingRenderer
72
- }
73
-
74
- /**
75
- * @public
76
- */
77
- export function portableTextToMarkdown<
78
- Block extends TypedObject = PortableTextBlock | ArbitraryTypedObject,
79
- >(blocks: Array<Block>, options: Options = {}): string {
80
- const renderers = {
81
- block: {
82
- ...defaultRenderers.block,
83
- ...options.block,
84
- },
85
- listItem: options.listItem ?? defaultRenderers.listItem,
86
- marks: {
87
- ...defaultRenderers.marks,
88
- ...options.marks,
89
- },
90
- types: {
91
- ...defaultRenderers.types,
92
- ...options.types,
93
- },
94
- hardBreak: options.hardBreak ?? defaultRenderers.hardBreak,
95
- unknownType: options.unknownType ?? defaultRenderers.unknownType,
96
- unknownBlockStyle:
97
- options.unknownBlockStyle ?? defaultRenderers.unknownBlockStyle,
98
- unknownListItem:
99
- options.unknownListItem ?? defaultRenderers.unknownListItem,
100
- unknownMark: options.unknownMark ?? defaultRenderers.unknownMark,
101
- }
102
- const renderBlockSpacing = options.blockSpacing ?? DefaultBlockSpacingRenderer
103
-
104
- const listIndexMap = buildListIndexMap(blocks)
105
- const renderNode = createRenderNode(renderers, listIndexMap)
106
-
107
- return blocks
108
- .map((node, index) => {
109
- const renderedNode = renderNode({
110
- node,
111
- index,
112
- isInline: false,
113
- renderNode,
114
- })
115
-
116
- if (index === blocks.length - 1) {
117
- return renderedNode
118
- }
119
-
120
- const nextNode = blocks.at(index + 1)
121
-
122
- if (!nextNode) {
123
- return renderedNode
124
- }
125
-
126
- const blockSpacing =
127
- renderBlockSpacing({
128
- current: node,
129
- next: nextNode,
130
- }) ?? '\n\n'
131
-
132
- return `${renderedNode}${blockSpacing}`
133
- })
134
- .join('')
135
- }
@@ -1,176 +0,0 @@
1
- import {
2
- buildMarksTree,
3
- isPortableTextBlock,
4
- isPortableTextListItemBlock,
5
- isPortableTextToolkitSpan,
6
- isPortableTextToolkitTextNode,
7
- spanToPlainText,
8
- type ToolkitNestedPortableTextSpan,
9
- type ToolkitTextNode,
10
- } from '@portabletext/toolkit'
11
- import type {
12
- PortableTextBlock,
13
- PortableTextListItemBlock,
14
- PortableTextMarkDefinition,
15
- PortableTextSpan,
16
- TypedObject,
17
- } from '@portabletext/types'
18
- import {defaultKeyGenerator} from '../key-generator'
19
- import type {PortableTextRenderers, RenderNode, Serializable} from './types'
20
-
21
- interface SerializedBlock {
22
- _key: string
23
- children: string
24
- index: number
25
- isInline: boolean
26
- node: PortableTextBlock | PortableTextListItemBlock
27
- }
28
-
29
- export const createRenderNode = (
30
- renderers: PortableTextRenderers,
31
- listIndexMap: Map<string, number>,
32
- ): RenderNode => {
33
- function renderNode<N extends TypedObject>(options: Serializable<N>): string {
34
- const {node, index, isInline} = options
35
-
36
- if (isPortableTextListItemBlock(node)) {
37
- return renderListItem(node, index)
38
- }
39
-
40
- if (isPortableTextToolkitSpan(node)) {
41
- return renderSpan(node)
42
- }
43
-
44
- if (isPortableTextBlock(node)) {
45
- return renderBlock(node, index, isInline)
46
- }
47
-
48
- if (isPortableTextToolkitTextNode(node)) {
49
- return renderText(node)
50
- }
51
-
52
- return renderCustomBlock(node, index, isInline)
53
- }
54
-
55
- function renderListItem(
56
- node: PortableTextListItemBlock<
57
- PortableTextMarkDefinition,
58
- PortableTextSpan
59
- >,
60
- index: number,
61
- ): string {
62
- const renderer = renderers.listItem
63
- const handler =
64
- typeof renderer === 'function' ? renderer : renderer[node.listItem]
65
- const itemHandler = handler || renderers.unknownListItem
66
-
67
- // Build the text content from the block
68
- const tree = buildMarksTree(node)
69
- const textContent = tree
70
- .map((child, i) => {
71
- return renderNode({node: child, isInline: true, index: i, renderNode})
72
- })
73
- .join('')
74
-
75
- let children = textContent
76
-
77
- if (node.style && node.style !== 'normal') {
78
- // Wrap any other style in whatever the block component says to use
79
- const {listItem: _listItem, ...blockNode} = node
80
- children = renderNode({
81
- node: blockNode,
82
- index,
83
- isInline: false,
84
- renderNode,
85
- })
86
- // Strip trailing newlines from block styles - list item component handles spacing
87
- children = children.replace(/\n+$/, '')
88
- }
89
-
90
- return itemHandler({
91
- value: node,
92
- index,
93
- listIndex: node._key ? listIndexMap.get(node._key) : undefined,
94
- isInline: false,
95
- renderNode,
96
- children,
97
- })
98
- }
99
-
100
- function renderSpan(node: ToolkitNestedPortableTextSpan): string {
101
- const {markDef, markType, markKey} = node
102
- const span = renderers.marks[markType] || renderers.unknownMark
103
- const children = node.children.map((child, childIndex) =>
104
- renderNode({node: child, index: childIndex, isInline: true, renderNode}),
105
- )
106
-
107
- return span({
108
- text: spanToPlainText(node),
109
- value: markDef,
110
- markType,
111
- markKey,
112
- renderNode,
113
- children: children.join(''),
114
- })
115
- }
116
-
117
- function renderBlock(
118
- node: PortableTextBlock,
119
- index: number,
120
- isInline: boolean,
121
- ): string {
122
- const {_key, ...props} = serializeBlock({node, index, isInline, renderNode})
123
- const style = props.node.style || 'normal'
124
- const handler =
125
- typeof renderers.block === 'function'
126
- ? renderers.block
127
- : renderers.block[style]
128
- const block = handler || renderers.unknownBlockStyle
129
-
130
- return block({...props, value: props.node, renderNode})
131
- }
132
-
133
- function renderText(node: ToolkitTextNode): string {
134
- if (node.text === '\n') {
135
- return renderers.hardBreak()
136
- }
137
-
138
- return node.text
139
- }
140
-
141
- function renderCustomBlock(
142
- value: TypedObject,
143
- index: number,
144
- isInline: boolean,
145
- ): string {
146
- const component = renderers.types[value._type] ?? renderers.unknownType
147
-
148
- return component({
149
- value,
150
- isInline,
151
- index,
152
- renderNode,
153
- })
154
- }
155
-
156
- return renderNode
157
- }
158
-
159
- function serializeBlock(
160
- options: Serializable<PortableTextBlock>,
161
- ): SerializedBlock {
162
- const {node, index, isInline, renderNode} = options
163
- const tree = buildMarksTree(node)
164
-
165
- const renderedChildren = tree.map((child, i) =>
166
- renderNode({node: child, isInline: true, index: i, renderNode}),
167
- )
168
-
169
- return {
170
- _key: node._key || defaultKeyGenerator(),
171
- children: renderedChildren.join(''),
172
- index,
173
- isInline,
174
- node,
175
- }
176
- }
@@ -1,39 +0,0 @@
1
- import {
2
- isPortableTextBlock,
3
- isPortableTextListItemBlock,
4
- } from '@portabletext/toolkit'
5
- import type {TypedObject} from '@portabletext/types'
6
-
7
- /**
8
- * @public
9
- */
10
- export type BlockSpacingRenderer = (options: {
11
- current: TypedObject
12
- next: TypedObject
13
- }) => string | undefined
14
-
15
- /**
16
- * @public
17
- */
18
- export const DefaultBlockSpacingRenderer: BlockSpacingRenderer = ({
19
- current,
20
- next,
21
- }) => {
22
- if (
23
- isPortableTextListItemBlock(current) &&
24
- isPortableTextListItemBlock(next)
25
- ) {
26
- return '\n'
27
- }
28
-
29
- if (
30
- isPortableTextBlock(current) &&
31
- isPortableTextBlock(next) &&
32
- current.style === 'blockquote' &&
33
- next.style === 'blockquote'
34
- ) {
35
- return '\n>\n'
36
- }
37
-
38
- return '\n\n'
39
- }
@@ -1,4 +0,0 @@
1
- /**
2
- * @public
3
- */
4
- export const DefaultHardBreakRenderer = (): string => ' \n'