fix: blockquote newline (#894)

This commit is contained in:
Daniel Lautzenheiser 2023-09-24 10:34:16 -04:00 committed by GitHub
parent d1cf12ec84
commit 69d11e831e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 317 additions and 62 deletions

View File

@ -1,7 +1,10 @@
import { isNotNullish, isNullish } from './null.util';
export function isEmpty(value: string | null | undefined): value is null | undefined {
return isNullish(value) || value === '';
export function isEmpty(
value: string | null | undefined,
ignoreWhitespace?: boolean,
): value is null | undefined {
return isNullish(value) || (ignoreWhitespace ? value.trim() === '' : value === '');
}
export function isNotEmpty(value: string | null | undefined): value is string {

View File

@ -2,5 +2,6 @@
@apply border-l-2
border-gray-400
ml-2
pl-2;
pl-2
my-2;
}

View File

@ -6,7 +6,6 @@ import type { MdPlatePlugin } from '@staticcms/markdown';
const softBreakPlugin: Partial<MdPlatePlugin<SoftBreakPlugin>> = {
options: {
rules: [
{ hotkey: 'shift+enter' },
{
hotkey: 'enter',
query: {

View File

@ -108,7 +108,9 @@ function serializeMarkdownNode(
childrenHasLink = chunk.children.some(f => !isLeafNode(f) && f.type === NodeTypes.link);
}
return serializeMarkdownNode(
return {
type: 'type' in c ? c.type : undefined,
response: serializeMarkdownNode(
{ ...c, parentType: type },
{
// WOAH.
@ -132,9 +134,34 @@ function serializeMarkdownNode(
index: childIndex,
shortcodeConfigs,
},
);
),
};
})
.join(separator);
.map(({ response, type }) => {
if (selfIsBlockquote) {
let serializedChild = response;
if (listDepth === 0) {
serializedChild = serializedChild.replace(
/(?<!(?:[ ]*(?:-|1.) [^\n]*)|[\n])[\n]{1}([^\n])/g,
' \n$1',
);
}
return { response: serializedChild, type };
}
return { response, type };
})
.reduce((acc, { response, type }, index) => {
if (selfIsBlockquote) {
if (type === NodeTypes.block_quote) {
return index === 0 ? response : `${acc}${separator}\n${response}`;
}
}
return index === 0 ? response : `${acc}${separator}${response}`;
}, '');
}
// This is pretty fragile code, check the long comment where we iterate over children
@ -152,7 +179,7 @@ function serializeMarkdownNode(
}
if (children === '' && !VOID_ELEMENTS.find(k => NodeTypes[k] === type)) {
return;
return '\n';
}
// Never allow decorating break tags with rich text formatting,
@ -237,10 +264,11 @@ function serializeMarkdownNode(
return `###### ${handleInBlockNewline(children)}\n`;
case NodeTypes.block_quote:
return `${selfIsBlockquote && blockquoteDepth > 0 ? '\n' : ''}> ${children
.replace(/^[\n]*|[\n]*$/gm, '')
return `> ${children
.replace(/[\n]+$/g, '')
.split('\n')
.join('\n> ')}\n`;
.join('\n> ')
.replace(/\n>[ \t]*\n/g, '\n>\n')}${selfIsBlockquote && blockquoteDepth === 0 ? '\n' : ''}`;
case NodeTypes.code_block:
const codeBlock = chunk as MdCodeBlockElement;
@ -319,7 +347,9 @@ ${bodyRows.join('\n')}`;
case NodeTypes.tableHeaderCell:
case NodeTypes.tableCell:
return isEmpty(children) ? ' ' : children.replace(/\|/g, '\\|').replace(/\n/g, BREAK_TAG);
return isEmpty(children, true)
? ' '
: children.replace(/\|/g, '\\|').replace(/\n/g, BREAK_TAG);
case NodeTypes.shortcode:
const shortcodeNode = chunk as MdShortcodeElement;

View File

@ -92,6 +92,8 @@ export interface Options {
isInTable?: boolean;
isInLink?: boolean;
isInTableHeaderRow?: boolean;
isInBlockquote?: boolean;
isInList?: boolean;
tableAlign?: (string | null)[];
useMdx: boolean;
shortcodeConfigs: Record<string, ShortcodeConfig>;
@ -105,6 +107,8 @@ export default function deserializeMarkdown(node: MdastNode, options: Options) {
isInTable = false,
isInLink = false,
isInTableHeaderRow = false,
isInBlockquote = false,
isInList = false,
tableAlign,
useMdx,
shortcodeConfigs,
@ -114,6 +118,8 @@ export default function deserializeMarkdown(node: MdastNode, options: Options) {
const selfIsTable = node.type === 'table';
const selfIsLink = node.type === 'link';
const selfIsTableHeaderRow = node.type === 'tableRow' && index === 0;
const selfIsBlockquote = node.type === 'blockquote';
const selfIsList = node.type === 'list';
const nodeChildren = node.children;
if (nodeChildren && Array.isArray(nodeChildren) && nodeChildren.length > 0) {
@ -128,6 +134,8 @@ export default function deserializeMarkdown(node: MdastNode, options: Options) {
isInTable: selfIsTable || isInTable,
isInLink: selfIsLink || isInLink,
isInTableHeaderRow: selfIsTableHeaderRow || isInTableHeaderRow,
isInBlockquote: selfIsBlockquote || isInBlockquote,
isInList: selfIsList || isInList,
useMdx,
shortcodeConfigs,
index: childIndex,
@ -181,7 +189,25 @@ export default function deserializeMarkdown(node: MdastNode, options: Options) {
} as ListItemNode;
case 'paragraph':
if ('ordered' in node) {
if (isInBlockquote || isInList) {
if (isInBlockquote && index > 0) {
if (children.length > 0) {
let firstChild = children[0];
if ('text' in firstChild) {
firstChild = { text: `\n\n${firstChild.text}` };
}
if (children.length > 1) {
const [_, ...rest] = children;
return [firstChild, ...rest];
}
return [firstChild];
}
return children;
}
return children;
}
@ -212,7 +238,20 @@ export default function deserializeMarkdown(node: MdastNode, options: Options) {
} as ImageNode;
case 'blockquote':
return { type: NodeTypes.block_quote, children } as BlockQuoteNode;
const blockquoteChildren = children.reduce((acc, n) => {
const lastNode = acc.length > 0 ? acc[acc.length - 1] : null;
if (lastNode && 'text' in lastNode && lastNode.text && 'text' in n && n.text) {
acc[acc.length - 1] = {
text: `${lastNode.text}${n.text}`,
};
} else {
acc.push(n);
}
return acc;
}, [] as DeserializedNode[]);
return { type: NodeTypes.block_quote, children: blockquoteChildren } as BlockQuoteNode;
case 'code':
return {
@ -230,7 +269,7 @@ export default function deserializeMarkdown(node: MdastNode, options: Options) {
children: [{ text: node.value?.replace(/<br>/g, '') || '' }],
} as ParagraphNode;
}
return { type: 'p', children: [{ text: node.value || '' }] };
return { type: 'p', children: [{ text: node.value ?? '' }] };
case 'emphasis':
return {
@ -285,7 +324,7 @@ export default function deserializeMarkdown(node: MdastNode, options: Options) {
}
}
return { text: node.value || '' };
return { text: node.value ?? '' };
case 'mdxJsxTextElement':
if ('name' in node && node.type === 'mdxJsxTextElement') {
@ -344,26 +383,37 @@ export default function deserializeMarkdown(node: MdastNode, options: Options) {
}
}
return { text: node.value || '' };
return { text: node.value ?? '' };
case 'break':
return { text: '\n' };
case 'text':
if (useMdx) {
return { text: node.value || '' };
return { text: (node.value ?? '').replace(/(?<![\n]|[ ]{2})[\n]{1}([^\n])/g, ' $1') };
}
if (!node.value) {
return { text: '' };
}
const nodes = autoLinkToSlate(
let nodes = autoLinkToSlate(
processShortcodeConfigsToSlate(shortcodeConfigs, [node]),
isInLink,
);
return nodes.map(node => (node.type === 'text' ? { text: node.value ?? '' } : node));
nodes = nodes.map(n => {
if (n.type !== 'text') {
return n;
}
return { text: (n.value ?? '').replace(/(?<![\n]|[ ]{2})[\n]{1}([^\n])/g, ' $1') };
});
return nodes;
default:
console.warn('[StaticCMS] Unrecognized mdast node, proceeding as text', node);
return { text: node.value || '' };
return { text: node.value ?? '' };
}
}

View File

@ -75,14 +75,13 @@ const serializationTestData: SerializationTests = {
},
'paragraph with line break': {
markdown: `A line of text
With another in the same paragraph`,
markdown: `A line of text with another in the same paragraph`,
slate: [
{
type: ELEMENT_PARAGRAPH,
children: [
{
text: 'A line of text\nWith another in the same paragraph',
text: 'A line of text with another in the same paragraph',
},
],
},
@ -234,20 +233,6 @@ And a completely new paragraph`,
],
},
'multiline blockquote': {
markdown: '> I am a block quote\n> And another line',
slate: [
{
type: ELEMENT_BLOCKQUOTE,
children: [
{
text: 'I am a block quote\nAnd another line',
},
],
},
],
},
'nested blockquote': {
markdown: '> I am a block quote\n> > And another line',
slate: [
@ -269,6 +254,116 @@ And a completely new paragraph`,
},
] as MdValue,
},
'multiline blockquote (double space and carrot each line)': {
markdown: '> One line \n> Another line',
slate: [
{
type: ELEMENT_BLOCKQUOTE,
children: [
{
text: 'One line\nAnother line',
},
],
},
] as MdValue,
},
'multiline blockquote (empty line carrot)': {
markdown: '> One line\n>\n> Another line',
slate: [
{
type: ELEMENT_BLOCKQUOTE,
children: [
{
text: 'One line\n\nAnother line',
},
],
},
] as MdValue,
},
'sequential blockquote': {
markdown: `> I am a block quote
> And another block quote`,
slate: [
{
type: ELEMENT_BLOCKQUOTE,
children: [
{
text: 'I am a block quote',
},
],
},
{
type: ELEMENT_BLOCKQUOTE,
children: [
{
text: 'And another block quote',
},
],
},
] as MdValue,
},
'blockquote with link': {
// First line has double space
markdown: `> I am a [block quote](https://example.com/). Another line
>
> Final Line`,
slate: [
{
type: ELEMENT_BLOCKQUOTE,
children: [
{
text: 'I am a ',
},
{
type: ELEMENT_LINK,
url: 'https://example.com/',
children: [
{
text: 'block quote',
},
],
},
{
text: '. Another line\n\nFinal Line',
},
],
},
],
},
'blockquote with link (no punctuation)': {
// First line has double space
markdown: `> I am a [block quote](https://example.com/) and another line
>
> Final Line`,
slate: [
{
type: ELEMENT_BLOCKQUOTE,
children: [
{
text: 'I am a ',
},
{
type: ELEMENT_LINK,
url: 'https://example.com/',
children: [
{
text: 'block quote',
},
],
},
{
text: ' and another line\n\nFinal Line',
},
],
},
],
},
},
},
@ -1543,8 +1638,7 @@ label: 'Blog post content',
widget: 'markdown',
\`\`\`
> See the table below for default options
> More API information can be found in the document
> See the table below for default options \n> More API information can be found in the document
|Name|Type|Default|Description|
|---|---|---|---|
@ -2683,8 +2777,86 @@ Text ahead [youtube|p6h-rYSVX90] and behind and another {{< twitter 917359331535
};
export const deserializationOnlyTestData: SerializationTests = {
blcokquote: {
both: {
'blockquote with link': {
// First line has double space
markdown: `> I am a [block quote](https://example.com/).
> Another line
>
> Final Line`,
slate: [
{
type: ELEMENT_BLOCKQUOTE,
children: [
{
text: 'I am a ',
},
{
type: ELEMENT_LINK,
url: 'https://example.com/',
children: [
{
text: 'block quote',
},
],
},
{
text: '. Another line\n\nFinal Line',
},
],
},
],
},
'multiline blockquote (carrot each line)': {
markdown: `> One line
> Another line`,
slate: [
{
type: ELEMENT_BLOCKQUOTE,
children: [
{
text: 'One line Another line',
},
],
},
] as MdValue,
},
'multiline blockquote (double space)': {
markdown: '> One line \nAnother line',
slate: [
{
type: ELEMENT_BLOCKQUOTE,
children: [
{
text: 'One line\nAnother line',
},
],
},
] as MdValue,
},
},
},
paragraph: {
markdown: {
'paragraph with line break': {
markdown: `A line of text
With another in the same paragraph`,
slate: [
{
type: ELEMENT_PARAGRAPH,
children: [
{
text: 'A line of text With another in the same paragraph',
},
],
},
],
},
'paragraph with link': {
markdown:
'A line of text with a link https://www.youtube.com/watch?v=p6h-rYSVX90 and some more text',