This repository has been archived by the owner on Nov 30, 2024. It is now read-only.
forked from caseywebdev/formatted-text
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathformatted-text.es6
102 lines (84 loc) · 2.62 KB
/
formatted-text.es6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import React from 'react';
import PropTypes from 'prop-types';
const PARAGRAPH_SPLIT = /\n{2,}/;
const LINE_SPLIT = /\n/;
const LINK = /(?:\S+)(\w+:\/\/)\S*|([^\s.]+)(\.[a-z-]{2,63})+\S*/gi;
const DEFAULT_PROTOCOL = 'http://';
const TERMINATORS = '.,;:?!';
const WRAPPERS = {
'(': ')',
'[': ']',
'"': '"',
"'": "'",
'<': '>'
};
const unwrap = (text, index) => {
const [first, last] = [text[0], text[text.length - 1]];
if (TERMINATORS.indexOf(last) > -1) return unwrap(text.slice(0, -1), index);
if (WRAPPERS[first] === last) return unwrap(text.slice(1, -1), index + 1);
return [text, index];
};
const getLinks = text => {
const links = [];
let match;
while (match = LINK.exec(text)) {
let [all, protocol, preTld, tld] = match;
// To qualify as a link, either the protocol or TLD must be specified.
if (!protocol && !tld) continue;
let {index} = match;
[all, index] = unwrap(all, index);
links.push({
index,
text: all,
url:
protocol ? all :
preTld.indexOf('@') !== -1 ? `mailto:${all}` :
DEFAULT_PROTOCOL + all
});
}
return links;
};
const KeyProxy = ({children}) => children;
const renderLinks = (text, key, props) => {
const links = getLinks(text);
if (!links.length) return text;
const {length} = links;
return links.reduce((parts, link, i) => {
const from = link.index;
const to = from + link.text.length;
return {
index: to,
components: parts.components.concat(
text.slice(parts.index, from),
<KeyProxy key={`${key}-${i}`}>{props.linkRenderer(link)}</KeyProxy>,
i === length - 1 ? text.slice(to) : null
)
};
}, {index: 0, components: []}).components;
};
const renderParagraph = (text, props) => {
const lines = text.trim().split(LINE_SPLIT);
return lines.reduce((paragraph, line, i) => paragraph.concat(
renderLinks(line, i, props),
i === lines.length - 1 ? null : <br key={i} />
), []);
};
const renderParagraphs = props => {
let {children} = props;
if (typeof children !== 'string') children = '';
const paragraphs = children.trim().split(PARAGRAPH_SPLIT);
return paragraphs.map((paragraph, i) =>
paragraph ? <p key={i}>{renderParagraph(paragraph, props)}</p> : null
);
};
const FormattedText = ({children, linkRenderer, ...divProps}) =>
<div {...divProps}>{renderParagraphs({children, linkRenderer})}</div>;
FormattedText.propTypes = {
children: PropTypes.string.isRequired,
linkRenderer: PropTypes.func.isRequired
};
FormattedText.defaultProps = {
children: '',
linkRenderer: ({url, text}) => <a href={url}>{text}</a>
};
export default FormattedText;