Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
// Remove a trailing newline because sometimes it sneaks in from when we add the newline to create the initial block
const content = Styles.isMobile ? capture[1].replace(/\n$/, '') : capture[1]
return {
content: SimpleMarkdown.parseInline(parse, content, {...state, inParagraph: true}),
}
},
},
quotedFence: {
// The ``` code blocks in a quote block >
// i.e.
// > They wrote ```
// foo = true
// ```
// It's much easier and cleaner to make this a separate rule
...SimpleMarkdown.defaultRules.fence,
match: SimpleMarkdown.anyScopeRegex(/^(?: *> *((?:[^\n](?!```))*)) ```\n?((?:\\[\s\S]|[^\\])+?)```\n?/),
// Example: https://regex101.com/r/ZiDBsO/6
order: SimpleMarkdown.defaultRules.blockQuote.order - 0.5,
parse: function(capture, parse, state) {
const preContent =
Styles.isMobile && !!capture[1]
? wrapInParagraph(parse, capture[1], state)
: SimpleMarkdown.parseInline(parse, capture[1], state)
return {
content: [
...preContent,
{
content: capture[2],
type: 'fence',
},
],
type: 'blockQuote',
},
// we prevent matching against text if we're mobile and we aren't in a paragraph. This is because
// in Mobile you can't have text outside a text tag, and a paragraph is what adds the text tag.
// This is just a fallback (note the order) in case nothing else matches. It wraps the content in
// a paragraph and tries to match again. Won't fallback on itself. If it's already in a paragraph,
// it won't match.
fallbackParagraph: {
// $FlowIssue - tricky to get this to type properly
match: (source, state, lookBehind) => (Styles.isMobile && !state.inParagraph ? [source] : null),
order: 10000,
parse: (capture, parse, state) => wrapInParagraph(parse, capture[0], state),
},
fence: {
// aka the ``` code blocks
...SimpleMarkdown.defaultRules.fence,
match: SimpleMarkdown.anyScopeRegex(/^```(?:\n)?((?:\\[\s\S]|[^\\])+?)```(?!`)(\n)?/),
// original:
// match: SimpleMarkdown.blockRegex(/^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n *)+\n/),
// ours: three ticks (anywhere) and remove any newlines in front and one in back
order: 0,
parse: function(capture, parse, state) {
return {
content: capture[1],
lang: undefined,
type: 'fence',
}
},
},
inlineCode: {
...SimpleMarkdown.defaultRules.inlineCode,
// original:
// match: inlineRegex(/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/),
...defaultRules.link,
react: (node, output, state) => (
<a rel="noopener noreferrer nofollow ugc" title="{node.title}" href="{node.target}">
{output(node.content, state)}
</a>
),
},
autolink: {
...defaultRules.autolink,
match: anyScopeRegex(/^<(https?:\/\/[^ >]+)>/),
},
url: defaultRules.url,
strong: defaultRules.strong,
em: defaultRules.em,
underline: defaultRules.u,
inlineCode: {
...defaultRules.inlineCode,
react: (node, _, state) => <code>{node.content}</code>,
},
shrug: {
// Edge case for shrug emoji getting parsed as markup.
order: defaultRules.text.order,
match: inlineRegex(/^¯\\_\(ツ\)_\/¯/),
parse: capture => ({
type: "text",
content: capture[0],
return matches
}
return null
},
order: SimpleMarkdown.defaultRules.text.order - 0.4,
parse: function(capture, parse, state) {
return {content: capture[2], mailto: `mailto:${capture[2]}`, spaceInFront: capture[1]}
},
},
newline: {
// handle newlines, keep this to handle \n w/ other matchers
...SimpleMarkdown.defaultRules.newline,
// original
// match: blockRegex(/^(?:\n *)*\n/),
// ours: handle \n inside text also
match: SimpleMarkdown.anyScopeRegex(/^\n/),
},
paragraph: {
...SimpleMarkdown.defaultRules.paragraph,
// original:
// match: SimpleMarkdown.blockRegex(/^((?:[^\n]|\n(?! *\n))+)(?:\n *)+\n/),
// ours: allow simple empty blocks, stop before a block quote or a code block (aka fence)
match: SimpleMarkdown.blockRegex(/^((?:[^\n`]|(?:`(?!``))|\n(?!(?: *\n| *>)))+)\n?/),
parse: (capture, parse, state) => {
// Remove a trailing newline because sometimes it sneaks in from when we add the newline to create the initial block
const content = Styles.isMobile ? capture[1].replace(/\n$/, '') : capture[1]
return {
content: SimpleMarkdown.parseInline(parse, content, {...state, inParagraph: true}),
}
},
},
quotedFence: {
// react: function (node, output, state) {
// var className = node.lang
// ? 'markdown-code markdown-code-' + node.lang
// : undefined
// // code with syntax highlighting (?)
// return <code>
// {node.content}
// </code>
// }
// }),
// fence: assign(defaultRules.fence, 11, {
// match: blockRegex(/^ *(`{3,}|~{3,})(\S+)? *\n?([\s\S]+?)\s*\1\n*/)
// }), // uses style of codeBlock
link: {
order: 18,
match: anyScopeRegex(/^(https?:\/\/[^\s<]+[^<>.,:;"')\]\s])/),
parse: function (capture, recurseParse, state) {
return { content: capture[1] }
},
react: function (node, output, state) {
const onClick = (ev) => {
ev.preventDefault()
remote.shell.openExternal(node.content)
}
return <a href="{node.content}">{node.content}</a>
}
},
newlinePlus: {
order: 19,
match: blockRegex(/^(?:\n *){2,}\n/),
parse: ignoreCapture,
react: function (node, output, state) { return <div> }</div>
),
},
}
const inlineRules: ReactRules = {
...baseRules,
}
const blockRules: ReactRules = {
...baseRules,
newline: defaultRules.newline,
paragraph: defaultRules.paragraph,
codeBlock: {
order: defaultRules.codeBlock.order,
// eslint-disable-next-line unicorn/regex-shorthand
match: anyScopeRegex(/^```(?:([\da-z-]+?)\n+)?\n*([\S\s]+?)\n*```/i),
parse: (capture, _, state) => ({
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
language: capture[1]?.trim(),
content: capture[2],
inQuote: state.inQuote,
}),
react: (node, _, state) => (
),
},
mention: {
order: defaultRules.text.order,
// $FlowIssue treat this like a RegExp
const linkRegex: RegExp = {
exec: source => {
const result = _linkRegex.exec(source)
if (result) {
result.groups = {tld: result[4]}
return result
}
return null
},
}
// Only allow a small set of characters before a url
const beforeLinkRegex = /[\s/(]/
const inlineLinkMatch = SimpleMarkdown.inlineRegex(linkRegex)
const textMatch = SimpleMarkdown.anyScopeRegex(
new RegExp(
// [\s\S]+? any char, at least 1 - lazy
// (?= // Positive look ahead. It should have these chars ahead
// // This is kinda weird, but for the regex to terminate it should have these cases be true ahead of its termination
// [^0-9A-Za-z\s] not a character in this set. So don't terminate if there is still more normal chars to eat
// | [\u00c0-\uffff] OR any unicode char. If there is a weird unicode ahead, we terminate
// | [\w-_.]+@ // OR something that looks like it starts an email. If there is an email looking thing ahead stop here.
// | (\w+\.)+(${commonTlds.join('|')}) // OR there is a url with a common tld ahead. Stop if there's a common url ahead
// | \w+:\S // OR there's letters before a : so stop here.
// | $ // OR we reach the end of the line
// )
`^[\\s\\S]+?(?=[^0-9A-Za-z\\s]|[\\u00c0-\\uffff]|[\\w-_.]+@|(\\w+\\.)+(${commonTlds.join(
'|'
)})|\\n|\\w+:\\S|$)`
)
)
},
},
text: {
...text,
match: (source) => /^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff-]|\n\n|\n|\w+:\S|$)/.exec(source),
html(node, output, state) {
if (state.escapeHTML) {
return md.sanitizeText(node.content)
}
return node.content
},
},
br: {
...br,
match: md.anyScopeRegex(/^\n/),
},
emoji: {
order: md.defaultRules.strong.order,
match: (source) => /^:([a-zA-z_-]*):/.exec(source),
parse(capture) {
return {
id: capture[1],
}
},
html(node, output, state) {
return htmlTag(
'span',
'',
{
class: `emoji`,
'data-emoji': node.id,
react (node, recurseOutput, state) {
return (
<img alt="{`<:${node.name}:${node.id}" draggable="{false}">`}
title={node.name}
src={node.src}
key={state.key}
/>
)
}
},
text: {
...SimpleMarkdown.defaultRules.text,
match: SimpleMarkdown.anyScopeRegex(
new RegExp(
SimpleMarkdown
.defaultRules
.text
.match
.regex
.source
.replace('?=', '?=\n|\r|')
)
),
parse (capture, recurseParse, state) {
return state.nested ? {
content: capture[0]
} : recurseParse(translateSurrogatesToInlineEmoji(capture[0]), {
...state,
nested: true