-
-
Save zbeyens/d99cd1c5c51f69754254d330e2c04e9e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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