Skip to content

Instantly share code, notes, and snippets.

@hanford
Created May 27, 2020 22:49
Show Gist options
  • Save hanford/c4d9708e1b5d25e905b73cfac60cc85f to your computer and use it in GitHub Desktop.
Save hanford/c4d9708e1b5d25e905b73cfac60cc85f to your computer and use it in GitHub Desktop.
// @flow
import { LIST_TYPES } from './extensions/base';
type LeafType = {|
text: string,
strikeThrough?: boolean,
bold?: boolean,
italic?: boolean,
|};
type BlockType = {|
type: string,
parentType?: string,
link?: ?string,
children: Array<BlockType | LeafType>,
|};
export default function parseToMarkdown(
chunk: BlockType | LeafType,
ignoreParagraphNewline?: boolean = false,
listDepth?: number = 0,
) {
let text = chunk.text || '';
let type = chunk.type || '';
let children = type
? // if we have a type, we're a BlockType element which _always_ has a children array.
// $FlowFixMe
chunk.children
.map(c => {
const isList = LIST_TYPES.includes(c.type || '');
const selfIsList = LIST_TYPES.includes(chunk.type || '');
const childrenHasLink =
Array.isArray(chunk.children) && chunk.children.some(f => f.type && f.type === 'link');
return parseToMarkdown(
{ ...c, parentType: type },
// WOAH.
// what we're doing here is pretty tricky, it relates to the block below where
// we check for ignoreParagraphNewline and set type to paragraph.
// We want to strip out empty paragraphs sometimes, but other times we don't.
// If we're the descendant of a list, we know we don't want a bunch
// of whitespace. If we're parallel to a link we also don't want
// to respect neighboring paraphs
ignoreParagraphNewline || isList || selfIsList || childrenHasLink,
// track depth of nested lists so we can add proper spacing
LIST_TYPES.includes(c.type || '') ? listDepth + 1 : listDepth,
);
})
.join('')
: text;
// This is pretty fragile code, check the long comment where we iterate over children
if (!ignoreParagraphNewline && chunk.text === '' && chunk.parentType === 'paragraph') {
type = 'paragraph';
children = '<br>';
}
if (children === '') return;
if (chunk.bold) {
children = retainWhitespaceAndFormat(children, '**');
}
if (chunk.italic) {
children = retainWhitespaceAndFormat(children, '_');
}
if (chunk.strikeThrough) {
children = `~~${children}~~`;
}
switch (type) {
case 'heading_one':
return `# ${children}\n`;
case 'heading_two':
return `## ${children}\n`;
case 'block_quote':
// For some reason, marked is parsing blockquotes w/ one new line
// as contiued blockquotes, so adding two new lines ensures that doesn't
// happen
return `> ${children}\n\n`;
case 'link':
return `[${children}](${chunk.link || ''})`;
case 'ul_list':
case 'ol_list':
return `\n${children}\n`;
case 'list_item':
// We're not a LeafType here and flow doesn't get that
// $FlowFixMe
const isOL = chunk && chunk.parentType === 'ol_list';
let spacer = '';
for (let k = 0; listDepth > k; k++) {
if (isOL) {
// https://github.com/remarkjs/remark-react/issues/65
spacer += ' ';
} else {
spacer += ' ';
}
}
return `${spacer}${isOL ? '1.' : '-'} ${children}\n`;
case 'paragraph':
return `${children}\n`;
default:
return children;
}
}
// This function handles the case of a string like this: " foo "
// Where it would be invalid markdown to generate this: "** foo **"
// We instead, want to trim the whitespace out, apply formatting, and then
// bring the whitespace back. So our returned string looks like this: " **foo** "
function retainWhitespaceAndFormat(string: string, format: string) {
const left = string.trimLeft();
const right = string.trimRight();
let children = string.trim();
const fullFormat = `${format}${children}${format}`;
if (children.length === string.length) {
return fullFormat;
}
const leftFormat = `${format}${children}`;
if (left.length !== string.length) {
const diff = string.length - left.length;
children = `${new Array(diff + 1).join(' ')}${leftFormat}`;
} else {
children = leftFormat;
}
const rightFormat = `${children}${format}`;
if (right.length !== string.length) {
const diff = string.length - right.length;
children = `${rightFormat}${new Array(diff + 1).join(' ')}`;
} else {
children = rightFormat;
}
return children;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment