From 39e1d32c669545ccc30e0d424323c6a01317c4be Mon Sep 17 00:00:00 2001 From: eug-vs Date: Sun, 5 Apr 2020 12:56:41 +0300 Subject: refactor: move some logic to InlineSyntax.tsx --- src/lib/Markdown/CodeBlock.tsx | 2 +- src/lib/Markdown/Content.tsx | 2 +- src/lib/Markdown/InlineSyntax.tsx | 81 ++++++++++++++++++++++++++++++ src/lib/Markdown/Paragraph.tsx | 100 ++------------------------------------ src/lib/Markdown/types.ts | 4 ++ 5 files changed, 91 insertions(+), 98 deletions(-) create mode 100644 src/lib/Markdown/InlineSyntax.tsx (limited to 'src/lib/Markdown') 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 = ({ rawLines }) => { return ( -

+

{rawLines.map(line => <> {line}
)}

); 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 = ({ rawLines }) => { const codeBlockLines = rawLines.splice(0, closeIndex + 1).slice(0, closeIndex); result = } else { - result = + result = } 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 = { + 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 = ({ line }) => { + if (!line) return null; + + const matchConceal = regex.conceal.local.exec(line); + if (matchConceal) { + if (line[0] === '!') return {matchConceal[1]}; + return {matchConceal[1]}; + } + + const matchEmoji = line.match(regex.emoji.local); + if (matchEmoji) { + const emoji = emojiList.find(emoji => emoji.name === matchEmoji[1]); + return {emoji ? emoji.char : line}; + } + + const matchCode = line.match(regex.code.local); + if (matchCode) return {matchCode[1]}; + + const matchBold = line.match(regex.bold.local); + if (matchBold) return {matchBold[1]}; + + const matchItalic = line.match(regex.italic.local); + if (matchItalic) return {matchItalic[1]}; + + const matchStrikeThrough = line.match(regex.strikeThrough.local); + if (matchStrikeThrough) return {matchStrikeThrough[1]}; + + if (line.match(regex.rawLink.global)) return {line}; + + 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= { - // 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 = ({ data }) => { - if (!data) return null; - - const conceal = concealRegex.exec(data); - if (conceal) { - if (data[0] === '!') return {conceal[1]}; - return {conceal[1]}; - } - - if (data.match(rawLinkRegex)) return {data}; - - 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 = {emoji ? emoji.char : ''}; - } else span = {match[1]}; - } - }); - return span; -} - -const Paragraph: React.FC = ({ data }) => { - const result = data.split(splitter).map(span => ); +const Paragraph: React.FC = ({ line }) => { + const result = line.split(splitter).map(span => ); return

{result}

; } 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[]; } -- cgit v1.2.3