diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/Markdown/CodeBlock.tsx | 2 | ||||
| -rw-r--r-- | src/lib/Markdown/Content.tsx | 2 | ||||
| -rw-r--r-- | src/lib/Markdown/InlineSyntax.tsx | 81 | ||||
| -rw-r--r-- | src/lib/Markdown/Paragraph.tsx | 100 | ||||
| -rw-r--r-- | src/lib/Markdown/types.ts | 4 | 
5 files changed, 91 insertions, 98 deletions
| diff --git a/src/lib/Markdown/CodeBlock.tsx b/src/lib/Markdown/CodeBlock.tsx index ee2114e..2718647 100644 --- a/src/lib/Markdown/CodeBlock.tsx +++ b/src/lib/Markdown/CodeBlock.tsx @@ -3,7 +3,7 @@ import { ParserPropTypes } from './types';  const CodeBlock: React.FC<ParserPropTypes> = ({ rawLines }) => {    return ( -    <p style={{background: '#444444', padding: '8px' }}> +    <p style={{background: 'rgba(255, 255, 255, .1)', padding: '8px' }}>        {rawLines.map(line => <> {line} <br/> </>)}      </p>    ); diff --git a/src/lib/Markdown/Content.tsx b/src/lib/Markdown/Content.tsx index 2a9e2ee..d0a2193 100644 --- a/src/lib/Markdown/Content.tsx +++ b/src/lib/Markdown/Content.tsx @@ -20,7 +20,7 @@ const Content: React.FC<ParserPropTypes> = ({ rawLines }) => {      const codeBlockLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex);      result = <CodeBlock rawLines={codeBlockLines} />    } else { -    result = <Paragraph data={line} /> +    result = <Paragraph line={line} />    }    return ( diff --git a/src/lib/Markdown/InlineSyntax.tsx b/src/lib/Markdown/InlineSyntax.tsx new file mode 100644 index 0000000..bf5669d --- /dev/null +++ b/src/lib/Markdown/InlineSyntax.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Link } from '@material-ui/core'; +import axios from 'axios'; + +import { InlineParserPropTypes } from './types'; + +interface RegexPair { +  global: RegExp; +  local: RegExp; +} + +interface Emoji { +  name: string; +  char: string; +} + +const enclosureRegex = (e: string): RegexPair => ({ +  local: new RegExp(`${e}([^${e}]+)${e}`), +  global: new RegExp(`(${e}[^${e}]+${e})`) +}); + +const regex: Record<string, RegexPair> = { +  conceal: { +    global: /(!?\[.+?\]\(.+?\))/g, +    local: /!?\[(.+?)\]\((.+?)\)/ +  }, +  rawLink: { +    global: /((?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[.!/\\\w]*))?)/, +    local: /&^/ +  }, +  emoji: enclosureRegex(':'), +  bold: enclosureRegex('\\*\\*'), +  italic: enclosureRegex('\\*'), +  code: enclosureRegex('`'), +  strikeThrough: enclosureRegex('~~'), +} + +const splitter = new RegExp(Object.values(regex).map(pair => pair.global.source).join('|')); + +const emojiList: Emoji[] = []; +axios.get('https://unpkg.com/emojilib@2.4.0/emojis.json').then(response => { +  Object.keys(response.data).forEach(name => emojiList.push({ name, char: response.data[name].char })); +}); + + +const InlineSyntax: React.FC<InlineParserPropTypes> = ({ line }) => { +  if (!line) return null; + +  const matchConceal = regex.conceal.local.exec(line); +  if (matchConceal) { +    if (line[0] === '!') return <img src={matchConceal[2]} alt={matchConceal[1]} style={{ maxWidth: '100%', maxHeight: '100%' }} />; +    return <Link href={matchConceal[2]}>{matchConceal[1]}</Link>; +  } + +  const matchEmoji = line.match(regex.emoji.local); +  if (matchEmoji) { +    const emoji = emojiList.find(emoji => emoji.name === matchEmoji[1]); +    return <span>{emoji ? emoji.char : line}</span>; +  } + +  const matchCode = line.match(regex.code.local); +  if (matchCode) return <span style={{ background: 'rgba(255, 255, 255, .1)' }}>{matchCode[1]}</span>; + +  const matchBold = line.match(regex.bold.local); +  if (matchBold) return <b>{matchBold[1]}</b>; + +  const matchItalic = line.match(regex.italic.local); +  if (matchItalic) return <i>{matchItalic[1]}</i>; + +  const matchStrikeThrough = line.match(regex.strikeThrough.local); +  if (matchStrikeThrough) return <span style={{textDecoration: 'line-through' }}>{matchStrikeThrough[1]}</span>; + +  if (line.match(regex.rawLink.global)) return <Link href={line}>{line}</Link>; + +  return <>{line}</>; +} + + +export { splitter }; +export default InlineSyntax; + diff --git a/src/lib/Markdown/Paragraph.tsx b/src/lib/Markdown/Paragraph.tsx index 19f8382..f46199e 100644 --- a/src/lib/Markdown/Paragraph.tsx +++ b/src/lib/Markdown/Paragraph.tsx @@ -1,101 +1,9 @@  import React from 'react'; -import { Link } from '@material-ui/core'; -import axios from 'axios'; +import { InlineParserPropTypes } from './types'; +import InlineSyntax, { splitter } from './InlineSyntax'; -interface PropTypes { -  data: string; -} - -interface Emoji { -  name: string; -  char: string; -} - -type InlineRuleName = 'bold' | 'italic' | 'code' | 'strikeThrough' | 'emoji'; - -interface InlineRuleProperties { -  enclosure: string; -  style: React.CSSProperties; -  pattern?: RegExp; -} - -const inlineRules: Record<InlineRuleName, InlineRuleProperties>= { -  // Order matters - lowest property has highest priority -  strikeThrough: { -    enclosure: '~~', -    style: { textDecoration: 'line-through' }, -  }, -  code: { -    enclosure: '`', -    style: { background: '#444444', padding: '4px' }, -  }, -  italic: { -    enclosure: '\\*', -    style: { fontStyle: 'italic' }, -  }, -  bold: { -    enclosure: '\\*\\*', -    style: { fontWeight: 'bold' }, -  }, -  emoji: { -    enclosure: ':', -    style: {}, -  } -}; - -const inlineRuleNames = Object.keys(inlineRules) as InlineRuleName[]; - -const captureInline = (enclosure: string): RegExp => { -  return new RegExp(enclosure + '([^' + enclosure + ']+)' + enclosure); -} -const captureInlineSplit = (enclosure: string): string => { -  return '(' + enclosure + '[^' + enclosure + ']+' + enclosure + ')'; -} -const concealRegex = /!?\[(.+?)\]\((.+?)\)/; -const concealRegexSplit = /(!?\[.+?\]\(.+?\))/g; -const rawLinkRegex = /((?:(?:[A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)(?:(?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[.!/\\\w]*))?)/; - -const ruleSplitPatterns: string[] = [concealRegexSplit.source, rawLinkRegex.source]; -inlineRuleNames.forEach(name => { -  const enclosure = inlineRules[name].enclosure; -  inlineRules[name].pattern = captureInline(enclosure); -  ruleSplitPatterns.push(captureInlineSplit(enclosure)); -}); - -const splitter = new RegExp(ruleSplitPatterns.join('|')); - -const emojiList: Emoji[] = []; -axios.get('https://unpkg.com/emojilib@2.4.0/emojis.json').then(response => { -  Object.keys(response.data).forEach(name => emojiList.push({ name, char: response.data[name].char })); -}); - -const SyntaxSpan: React.FC<PropTypes> = ({ data }) => { -  if (!data) return null; - -  const conceal = concealRegex.exec(data); -  if (conceal) { -    if (data[0] === '!') return <img src={conceal[2]} alt={conceal[1]} style={{ maxWidth: '100%', maxHeight: '100%' }} />; -    return <Link href={conceal[2]}>{conceal[1]}</Link>; -  } - -  if (data.match(rawLinkRegex)) return <Link href={data}>{data}</Link>; - -  let span = <>{data}</>; -  inlineRuleNames.forEach(name => { -    const rule = inlineRules[name]; -    const match = data.match(rule.pattern || ''); -    if (match) { -      if (name === 'emoji') { -        const emoji = emojiList.find(emoji => emoji.name === match[1]); -        span = <span>{emoji ? emoji.char : ''}</span>; -      } else span = <span style={rule.style}>{match[1]}</span>; -    } -  }); -  return span; -} - -const Paragraph: React.FC<PropTypes> = ({ data }) => { -  const result = data.split(splitter).map(span => <SyntaxSpan data={span} />); +const Paragraph: React.FC<InlineParserPropTypes> = ({ line }) => { +  const result = line.split(splitter).map(span => <InlineSyntax line={span} />);    return <p> {result} </p>;  } diff --git a/src/lib/Markdown/types.ts b/src/lib/Markdown/types.ts index 0b6f4b6..8fb28ed 100644 --- a/src/lib/Markdown/types.ts +++ b/src/lib/Markdown/types.ts @@ -1,3 +1,7 @@ +export interface InlineParserPropTypes { +  line: string; +} +  export interface ParserPropTypes {    rawLines: string[];  } | 
