/
dynamicTextFormater.ts
118 lines (99 loc) · 4.61 KB
/
dynamicTextFormater.ts
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import {isBip39Word} from '../wallet/mnemonic'
import {getCaretPosition, setCaretPosition} from './caretPosition'
import matchAll from './matchAll'
const underlineNonBip39words = (element, content) => {
const innerHTML = element.innerHTML
if (content !== innerHTML) {
const caretPosition = getCaretPosition(element)
let toFormat = innerHTML
// 1. forbid multiple divs (lines)
toFormat = toFormat.replace(/<\/{0,1}(div|br|p)[^<>]*>/g, '')
// 2. only non-breaking space allowed as whitespace
toFormat = toFormat.replace(/\s| /g, ' ')
// 3. wrap span around words at the start of line
// ^[^<>a-zA-Z]* - find start of line and absence of '<' and '>'
// [a-zA-Z]+ - find word itself
// (\s|[,;])+<span - find next opening tag '<span'
toFormat = toFormat.replace(
/(^[^<>a-zA-Z]*)([a-zA-Z]+)((\s|[,;])+<span)/g,
'$1<span>$2</span>$3'
)
// 4. wrap span around words in the middle of line
// <\/span>(\s|[,;])+ - find '</span>' before word
// [a-zA-Z]+ - find word itself
// (\s|[,;])+<span - find '<span' after word
toFormat = toFormat.replace(
/(<\/span>(\s|[,;])+)([a-zA-Z]+)((\s|[,;])+<span)/g,
'$1<span>$3</span>$4'
)
// 5. wrap span around words in the end of the line
// (?<=^|\s|[,;]) - check for delimiter before word
// [a-zA-Z]+ - find word itself
// (?=$|\s|[,;]) - check for delimiter after word
// (?=[^<>]*$) - check for absence of '<' or '>' after word to prevent
// wrapping tags inside span, like <span class="a b c">
toFormat = toFormat.replace(
/(?<=^|\s|[,;])[a-zA-Z]+(?=$|\s|[,;])(?=[^<>]*$)/g,
'<span>$&</span>'
)
// 6. split words by whitespace or commas
// (?<!<span) - check if space before word is inside span tag,
// we don't want to split that; eg. <span class="bip-39">
// (?<![>,;]|\s) - check for '>' symbol from previous span tag
// (\s|[,;])+ - find whitespaces or commas
// (?!\s|[<,;]) - check for '<' symbol from next span tag
toFormat = toFormat.replace(/(?<!<span)(?<![>,;]|\s)(\s|[,;])+(?!\s|[<,;])/g, '</span>$&<span>')
// 7. merge words if there is no whitespace between them
toFormat = toFormat.replace(/<\/span><span[^>]*>/g, '')
// 8. append to wrapped word
// <span[^<>]*> - find opening tag <span class="..."> before word
// [a-zA-Z]+ - find word itself
// <\/span> - find closing tag </span> after word
// [a-zA-Z]+ - find text to append
toFormat = toFormat.replace(/(<span[^<>]*>[a-zA-Z]+)(<\/span>)([a-zA-Z]+)/g, '$1$3$2')
// 9. preppend to wrapped word
// [a-zA-Z]+ - find text to prepend
// <span[^<>]*> - find opening tag <span class="..."> before word
// [a-zA-Z]+ - find word itself
// <\/span> - find closing tag </span> after word
toFormat = toFormat.replace(/([a-zA-Z]+)(<span[^<>]*>)([a-zA-Z]+)(<\/span>)/g, '$2$1$3$4')
// 10. extract delimiters outside of spans
// <span[^<>]*> - find opening tag <span class="..."> before word
// (\s|[,;])* - find delimiters to the left of word
// [a-zA-Z]+ - find word itself
// (\s|[,;])* - find delimiters to the right of word
// <\/span> - find closing tag </span> after word
toFormat = toFormat.replace(
/(<span[^<>]*>)(\s|[,;])*([a-zA-Z]+)(\s|[,;])*(<\/span>)/g,
'$2$1$3$5$4'
)
// 11 remove empty spans
toFormat = toFormat.replace(/<span[^>]*><\/span>/g, '')
// 12. translate non-breaking space back to html
// warning: this translates spaces inside span attributes as well,
// but it doesn't matter for the next steps
toFormat = toFormat.replace(/\s/g, ' ')
// 13. extract raw words into array
// <span[^>]*> - find opening tag
// [^<>]* - find element content
// <\/span>) - find closing tag
const words = matchAll(/<span[^>]*>[^<>]*<\/span>/g, toFormat).map((wrappedWord) =>
wrappedWord.replace(/(<span[^>]*>)([^<>]*)(<\/span>)/g, '$2').replace(/(\s|[,;])/g, '')
)
// 14. reapply style rules to each word
const areWordsBipP39 = words.map((word) => isBip39Word(word)).reverse()
const formattedText = toFormat.replace(
/(<span[^>]*>)([^<>]*)(<\/span>)/g,
(match, p1, p2, p3, offset, string) => {
const style = areWordsBipP39.pop() ? '' : ' class="not-bip-39"'
return `<span${style}>${p2}${p3}`
}
)
const rawText = words.join(' ')
element.innerHTML = `${formattedText}`
setCaretPosition(element, caretPosition)
return {formattedText, rawText}
}
return null
}
export {underlineNonBip39words}