@pie-lib/text-select 3.0.3-next.36 → 3.0.3-next.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/index.d.ts +15 -0
  2. package/dist/index.js +7 -0
  3. package/dist/legend.d.ts +13 -0
  4. package/dist/legend.js +64 -0
  5. package/dist/node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.js +16 -0
  6. package/dist/text-select.d.ts +34 -0
  7. package/dist/text-select.js +53 -0
  8. package/dist/token-select/index.d.ts +44 -0
  9. package/dist/token-select/index.js +170 -0
  10. package/dist/token-select/token.d.ts +32 -0
  11. package/dist/token-select/token.js +134 -0
  12. package/dist/tokenizer/builder.d.ts +27 -0
  13. package/dist/tokenizer/builder.js +124 -0
  14. package/dist/tokenizer/controls.d.ts +23 -0
  15. package/dist/tokenizer/controls.js +68 -0
  16. package/dist/tokenizer/index.d.ts +35 -0
  17. package/dist/tokenizer/index.js +91 -0
  18. package/dist/tokenizer/selection-utils.d.ts +10 -0
  19. package/dist/tokenizer/selection-utils.js +18 -0
  20. package/dist/tokenizer/token-text.d.ts +27 -0
  21. package/dist/tokenizer/token-text.js +85 -0
  22. package/dist/utils.d.ts +12 -0
  23. package/dist/utils.js +21 -0
  24. package/package.json +33 -30
  25. package/CHANGELOG.json +0 -1
  26. package/CHANGELOG.md +0 -946
  27. package/LICENSE.md +0 -5
  28. package/lib/index.js +0 -57
  29. package/lib/index.js.map +0 -1
  30. package/lib/legend.js +0 -119
  31. package/lib/legend.js.map +0 -1
  32. package/lib/text-select.js +0 -105
  33. package/lib/text-select.js.map +0 -1
  34. package/lib/token-select/index.js +0 -267
  35. package/lib/token-select/index.js.map +0 -1
  36. package/lib/token-select/token.js +0 -236
  37. package/lib/token-select/token.js.map +0 -1
  38. package/lib/tokenizer/builder.js +0 -265
  39. package/lib/tokenizer/builder.js.map +0 -1
  40. package/lib/tokenizer/controls.js +0 -106
  41. package/lib/tokenizer/controls.js.map +0 -1
  42. package/lib/tokenizer/index.js +0 -147
  43. package/lib/tokenizer/index.js.map +0 -1
  44. package/lib/tokenizer/selection-utils.js +0 -55
  45. package/lib/tokenizer/selection-utils.js.map +0 -1
  46. package/lib/tokenizer/token-text.js +0 -176
  47. package/lib/tokenizer/token-text.js.map +0 -1
  48. package/lib/utils.js +0 -51
  49. package/lib/utils.js.map +0 -1
  50. package/src/__tests__/legend.test.jsx +0 -211
  51. package/src/__tests__/text-select.test.jsx +0 -44
  52. package/src/__tests__/utils.test.jsx +0 -27
  53. package/src/index.js +0 -8
  54. package/src/legend.js +0 -102
  55. package/src/text-select.jsx +0 -79
  56. package/src/token-select/__tests__/index.test.jsx +0 -623
  57. package/src/token-select/__tests__/token.test.jsx +0 -236
  58. package/src/token-select/index.jsx +0 -242
  59. package/src/token-select/token.jsx +0 -223
  60. package/src/tokenizer/__tests__/builder.test.js +0 -256
  61. package/src/tokenizer/__tests__/controls.test.jsx +0 -27
  62. package/src/tokenizer/__tests__/index.test.jsx +0 -329
  63. package/src/tokenizer/__tests__/selection-utils.test.js +0 -145
  64. package/src/tokenizer/__tests__/token-text.test.jsx +0 -318
  65. package/src/tokenizer/builder.js +0 -258
  66. package/src/tokenizer/controls.jsx +0 -71
  67. package/src/tokenizer/index.jsx +0 -144
  68. package/src/tokenizer/selection-utils.js +0 -49
  69. package/src/tokenizer/token-text.jsx +0 -135
  70. package/src/utils.js +0 -56
@@ -1,258 +0,0 @@
1
- import { clone, compact } from 'lodash-es';
2
- import English from '@pie-framework/parse-english';
3
-
4
- const g = (str, node) => {
5
- if (node.children) {
6
- return node.children.reduce(g, str);
7
- } else if (node.value) {
8
- return str + node.value;
9
- } else {
10
- return str;
11
- }
12
- };
13
-
14
- const getParagraph = (p) => g('', p);
15
-
16
- const getSentence = (s) => g('', s);
17
-
18
- const getWord = (w) => g('', w);
19
-
20
- export const paragraphs = (text) => {
21
- const tree = new English().parse(text);
22
-
23
- const out = tree.children.reduce((acc, child) => {
24
- if (child.type === 'ParagraphNode') {
25
- const paragraph = {
26
- text: getParagraph(child),
27
- start: child.position.start.offset,
28
- end: child.position.end.offset,
29
- };
30
-
31
- return acc.concat([paragraph]);
32
- } else {
33
- return acc;
34
- }
35
- }, []);
36
-
37
- return out;
38
- };
39
-
40
- export const handleSentence = (child, acc) => {
41
- const sentenceChilds = [];
42
- // we parse the children of the sentence
43
- let newAcc = child.children.reduce(function (acc, child) {
44
- // if we find a whitespace node that's \n, we end the sentence
45
- if (child.type === 'WhiteSpaceNode' && child.value === '\n') {
46
- if (sentenceChilds.length) {
47
- const firstWord = sentenceChilds[0];
48
- // we create a sentence starting from the first word until the new line
49
- const sentence = {
50
- text: sentenceChilds.map((d) => getSentence(d)).join(''),
51
- start: firstWord.position.start.offset,
52
- end: child.position.start.offset,
53
- };
54
-
55
- // we remove all the elements from the array
56
- sentenceChilds.splice(0, sentenceChilds.length);
57
- return acc.concat([sentence]);
58
- }
59
- } else {
60
- // otherwise we add it to the array that contains the child forming a sentence
61
- sentenceChilds.push(child);
62
- }
63
-
64
- return acc;
65
- }, acc);
66
-
67
- // we treat the case when no \n character is found at the end
68
- // so we create a sentence from the last words or white spaces found
69
- if (sentenceChilds.length) {
70
- const firstWord = sentenceChilds[0];
71
- const lastWord = sentenceChilds[sentenceChilds.length - 1];
72
- const sentence = {
73
- text: sentenceChilds.map((d) => getSentence(d)).join(''),
74
- start: firstWord.position.start.offset,
75
- end: lastWord.position.end.offset,
76
- };
77
-
78
- newAcc = newAcc.concat([sentence]);
79
-
80
- sentenceChilds.splice(0, sentenceChilds.length);
81
- }
82
-
83
- return newAcc;
84
- };
85
-
86
- export const sentences = (text) => {
87
- const tree = new English().parse(text);
88
-
89
- const out = tree.children.reduce((acc, child) => {
90
- if (child.type === 'ParagraphNode') {
91
- return child.children.reduce((acc, child) => {
92
- if (child.type === 'SentenceNode') {
93
- const newAcc = handleSentence(child, acc);
94
-
95
- return newAcc || acc;
96
- } else {
97
- return acc;
98
- }
99
- }, acc);
100
- } else {
101
- return acc;
102
- }
103
- }, []);
104
-
105
- return out;
106
- };
107
- export const words = (text) => {
108
- const tree = new English().parse(text);
109
-
110
- const out = tree.children.reduce((acc, child) => {
111
- if (child.type === 'ParagraphNode') {
112
- return child.children.reduce((acc, child) => {
113
- if (child.type === 'SentenceNode') {
114
- return child.children.reduce((acc, child) => {
115
- if (child.type === 'WordNode') {
116
- const node = {
117
- text: getWord(child),
118
- start: child.position.start.offset,
119
- end: child.position.end.offset,
120
- };
121
- return acc.concat([node]);
122
- } else {
123
- return acc;
124
- }
125
- }, acc);
126
- } else {
127
- return acc;
128
- }
129
- }, acc);
130
- } else {
131
- return acc;
132
- }
133
- }, []);
134
-
135
- return out;
136
- };
137
-
138
- class Intersection {
139
- constructor(results) {
140
- this.results = results;
141
- }
142
-
143
- get hasOverlap() {
144
- return this.results.filter((r) => r.type === 'overlap').length > 0;
145
- }
146
-
147
- get surroundedTokens() {
148
- return this.results.filter((r) => r.type === 'within-selection').map((t) => t.token);
149
- }
150
- }
151
- /**
152
- * get intersection info for the selection in relation to tokens.
153
- * @param {{start: number, end: number}} selection
154
- * @param {{start: number, end: number}[]} tokens
155
- * @return {tokens: [], type: 'overlap|no-overlap|contains'}
156
- */
157
- export const intersection = (selection, tokens) => {
158
- const { start, end } = selection;
159
-
160
- const startsWithin = (t) => start >= t.start && start < t.end;
161
- const endsWithin = (t) => end > t.start && end <= t.end;
162
-
163
- const mapped = tokens.map((t) => {
164
- if (start === t.start && end === t.end) {
165
- return { token: t, type: 'exact-fit' };
166
- } else if (start <= t.start && end >= t.end) {
167
- return { token: t, type: 'within-selection' };
168
- } else if (startsWithin(t) || endsWithin(t)) {
169
- return { token: t, type: 'overlap' };
170
- }
171
- });
172
- return new Intersection(compact(mapped));
173
- };
174
-
175
- export const sort = (tokens) => {
176
- if (!Array.isArray(tokens)) {
177
- return tokens;
178
- } else {
179
- const out = clone(tokens);
180
- out.sort((a, b) => {
181
- const s = a.start < b.start ? -1 : a.start > b.start ? 1 : 0;
182
- const e = a.end < b.end ? -1 : a.end > b.end ? 1 : 0;
183
- if (s === -1 && e !== -1) {
184
- throw new Error(`sort does not support intersecting tokens. a: ${a.start}-${a.end}, b: ${b.start}-${b.end}`);
185
- }
186
- return s;
187
- });
188
- return out;
189
- }
190
- };
191
-
192
- export const normalize = (textToNormalize, tokens) => {
193
- // making sure text provided is a string
194
- const text = textToNormalize || '';
195
-
196
- if (!Array.isArray(tokens) || tokens.length === 0) {
197
- return [
198
- {
199
- text,
200
- start: 0,
201
- end: text.length,
202
- },
203
- ];
204
- }
205
-
206
- const out = sort(tokens).reduce(
207
- (acc, t, index, outer) => {
208
- let tokens = [];
209
- const lastIndex = acc.lastIndex;
210
-
211
- if (t.start === lastIndex) {
212
- tokens = [
213
- {
214
- text: text.substring(lastIndex, t.end),
215
- start: lastIndex,
216
- end: t.end,
217
- predefined: true,
218
- correct: t.correct,
219
- isMissing: t.isMissing,
220
- },
221
- ];
222
- } else if (lastIndex < t.start) {
223
- tokens = [
224
- {
225
- text: text.substring(lastIndex, t.start),
226
- start: lastIndex,
227
- end: t.start,
228
- },
229
- {
230
- text: text.substring(t.start, t.end),
231
- start: t.start,
232
- end: t.end,
233
- predefined: true,
234
- correct: t.correct,
235
- isMissing: t.isMissing,
236
- },
237
- ];
238
- }
239
-
240
- if (index === outer.length - 1 && t.end < text.length) {
241
- const last = {
242
- text: text.substring(t.end),
243
- start: t.end,
244
- end: text.length,
245
- };
246
- tokens.push(last);
247
- }
248
-
249
- return {
250
- lastIndex: tokens.length ? tokens[tokens.length - 1].end : lastIndex,
251
- result: acc.result.concat(tokens),
252
- };
253
- },
254
- { result: [], lastIndex: 0 },
255
- );
256
-
257
- return out.result;
258
- };
@@ -1,71 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import Button from '@mui/material/Button';
4
- import { styled } from '@mui/material/styles';
5
- import Switch from '@mui/material/Switch';
6
- import FormControlLabel from '@mui/material/FormControlLabel';
7
- import { color } from '@pie-lib/render-ui';
8
-
9
- const StyledControls = styled('div')(() => ({
10
- display: 'flex',
11
- alignItems: 'center',
12
- justifyContent: 'space-between',
13
- }));
14
-
15
- const StyledButton = styled(Button)(({ theme }) => ({
16
- marginRight: theme.spacing(1),
17
- }));
18
-
19
- const StyledSwitch = styled(Switch)(() => ({
20
- '& .MuiSwitch-thumb': {
21
- '&.Mui-checked': {
22
- color: `${color.tertiary()} !important`,
23
- },
24
- },
25
- '& .MuiSwitch-track': {
26
- '&.Mui-checked': {
27
- backgroundColor: `${color.tertiaryLight()} !important`,
28
- },
29
- },
30
- }));
31
-
32
- export class Controls extends React.Component {
33
- static propTypes = {
34
- onClear: PropTypes.func.isRequired,
35
- onWords: PropTypes.func.isRequired,
36
- onSentences: PropTypes.func.isRequired,
37
- onParagraphs: PropTypes.func.isRequired,
38
- setCorrectMode: PropTypes.bool.isRequired,
39
- onToggleCorrectMode: PropTypes.func.isRequired,
40
- };
41
-
42
- static defaultProps = {};
43
-
44
- render() {
45
- const { onClear, onWords, onSentences, onParagraphs, setCorrectMode, onToggleCorrectMode } = this.props;
46
-
47
- return (
48
- <StyledControls>
49
- <div>
50
- <StyledButton onClick={onWords} size="small" color="primary" disabled={setCorrectMode}>
51
- Words
52
- </StyledButton>
53
- <StyledButton onClick={onSentences} size="small" color="primary" disabled={setCorrectMode}>
54
- Sentences
55
- </StyledButton>
56
- <StyledButton onClick={onParagraphs} size="small" color="primary" disabled={setCorrectMode}>
57
- Paragraphs
58
- </StyledButton>
59
- <StyledButton size="small" color="secondary" onClick={onClear} disabled={setCorrectMode}>
60
- Clear
61
- </StyledButton>
62
- </div>
63
- <FormControlLabel
64
- control={<StyledSwitch checked={setCorrectMode} onChange={onToggleCorrectMode} />}
65
- label="Set correct answers"
66
- />
67
- </StyledControls>
68
- );
69
- }
70
- }
71
- export default Controls;
@@ -1,144 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import Controls from './controls';
4
- import { styled } from '@mui/material/styles';
5
- import { paragraphs, sentences, words } from './builder';
6
- import { clone, differenceWith, isEqual } from 'lodash-es';
7
- import { noSelect } from '@pie-lib/style-utils';
8
- import TokenText from './token-text';
9
-
10
- const StyledText = styled('div')(({ disabled }) => ({
11
- whiteSpace: 'pre-wrap',
12
-
13
- ...(disabled && {
14
- ...noSelect(),
15
- }),
16
- }));
17
-
18
- export class Tokenizer extends React.Component {
19
- static propTypes = {
20
- text: PropTypes.string.isRequired,
21
- tokens: PropTypes.arrayOf(
22
- PropTypes.shape({
23
- text: PropTypes.string,
24
- correct: PropTypes.bool,
25
- start: PropTypes.number,
26
- end: PropTypes.number,
27
- }),
28
- ),
29
- onChange: PropTypes.func.isRequired,
30
- };
31
-
32
- static defaultProps = {};
33
-
34
- constructor(props) {
35
- super(props);
36
- this.state = {
37
- setCorrectMode: false,
38
- mode: '',
39
- };
40
- }
41
-
42
- onChangeHandler = (token, mode) => {
43
- this.props.onChange(token, mode);
44
-
45
- this.setState({
46
- mode,
47
- });
48
- };
49
-
50
- toggleCorrectMode = () => this.setState({ setCorrectMode: !this.state.setCorrectMode });
51
-
52
- clear = () => {
53
- this.onChangeHandler([], '');
54
- };
55
-
56
- buildTokens = (type, fn) => {
57
- const { text } = this.props;
58
- const tokens = fn(text);
59
-
60
- this.onChangeHandler(tokens, type);
61
- };
62
-
63
- selectToken = (newToken, tokensToRemove) => {
64
- const { tokens } = this.props;
65
- const update = differenceWith(clone(tokens), tokensToRemove, isEqual);
66
-
67
- update.push(newToken);
68
- this.onChangeHandler(update, this.state.mode);
69
- };
70
-
71
- tokenClick = (token) => {
72
- const { setCorrectMode } = this.state;
73
-
74
- if (setCorrectMode) {
75
- this.setCorrect(token);
76
- } else {
77
- this.removeToken(token);
78
- }
79
- };
80
-
81
- tokenIndex = (token) => {
82
- const { tokens } = this.props;
83
-
84
- return tokens.findIndex((t) => {
85
- return t.text == token.text && t.start == token.start && t.end == token.end;
86
- });
87
- };
88
-
89
- setCorrect = (token) => {
90
- const { tokens } = this.props;
91
- const index = this.tokenIndex(token);
92
- if (index !== -1) {
93
- const t = tokens[index];
94
-
95
- t.correct = !t.correct;
96
-
97
- const update = clone(tokens);
98
-
99
- update.splice(index, 1, t);
100
- this.onChangeHandler(update, this.state.mode);
101
- }
102
- };
103
-
104
- removeToken = (token) => {
105
- const { tokens } = this.props;
106
-
107
- const index = this.tokenIndex(token);
108
- if (index !== -1) {
109
- const update = clone(tokens);
110
-
111
- update.splice(index, 1);
112
-
113
- this.onChangeHandler(update, this.state.mode);
114
- }
115
- };
116
-
117
- render() {
118
- const { text, tokens } = this.props;
119
- const { setCorrectMode } = this.state;
120
-
121
- return (
122
- <div>
123
- <Controls
124
- onClear={this.clear}
125
- onWords={() => this.buildTokens('words', words)}
126
- onSentences={() => this.buildTokens('sentence', sentences)}
127
- onParagraphs={() => this.buildTokens('paragraphs', paragraphs)}
128
- setCorrectMode={setCorrectMode}
129
- onToggleCorrectMode={this.toggleCorrectMode}
130
- />
131
- <StyledText
132
- disabled={setCorrectMode}
133
- as={TokenText}
134
- text={text}
135
- tokens={tokens}
136
- onTokenClick={this.tokenClick}
137
- onSelectToken={this.selectToken}
138
- />
139
- </div>
140
- );
141
- }
142
- }
143
-
144
- export default Tokenizer;
@@ -1,49 +0,0 @@
1
- export const clearSelection = () => {
2
- if (document.getSelection) {
3
- // for all new browsers (IE9+, Chrome, Firefox)
4
- document.getSelection().removeAllRanges();
5
- document.getSelection().addRange(document.createRange());
6
- } else if (window.getSelection) {
7
- // equals with the document.getSelection (MSDN info)
8
- if (window.getSelection().removeAllRanges) {
9
- // for all new browsers (IE9+, Chrome, Firefox)
10
- window.getSelection().removeAllRanges();
11
- window.getSelection().addRange(document.createRange());
12
- } else if (window.getSelection().empty) {
13
- // Chrome supports this as well
14
- window.getSelection().empty();
15
- }
16
- } else if (document.selection) {
17
- // IE8-
18
- document.selection.empty();
19
- }
20
- };
21
-
22
- export const getCaretCharacterOffsetWithin = (element) => {
23
- var caretOffset = 0;
24
- var doc = element.ownerDocument || element.document;
25
- var win = doc.defaultView || doc.parentWindow;
26
- var sel;
27
- if (typeof win.getSelection !== 'undefined') {
28
- sel = win.getSelection();
29
- if (sel.rangeCount > 0) {
30
- var range = win.getSelection().getRangeAt(0);
31
- var selected = range.toString().length;
32
- var preCaretRange = range.cloneRange();
33
- preCaretRange.selectNodeContents(element);
34
- preCaretRange.setEnd(range.endContainer, range.endOffset);
35
- if (selected) {
36
- caretOffset = preCaretRange.toString().length - selected;
37
- } else {
38
- caretOffset = preCaretRange.toString().length;
39
- }
40
- }
41
- } else if ((sel = doc.selection) && sel.type !== 'Control') {
42
- var textRange = sel.createRange();
43
- var preCaretTextRange = doc.body.createTextRange();
44
- preCaretTextRange.moveToElementText(element);
45
- preCaretTextRange.setEndPoint('EndToEnd', textRange);
46
- caretOffset = preCaretTextRange.text.length;
47
- }
48
- return caretOffset;
49
- };
@@ -1,135 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { styled } from '@mui/material/styles';
4
- import { intersection, normalize } from './builder';
5
- import debug from 'debug';
6
- import classNames from 'classnames';
7
-
8
- import { clearSelection, getCaretCharacterOffsetWithin } from './selection-utils';
9
-
10
- import { green, yellow } from '@mui/material/colors';
11
-
12
- const log = debug('@pie-lib:text-select:token-text');
13
-
14
- const StyledText = styled('span')(() => ({
15
- '&.predefined': {
16
- cursor: 'pointer',
17
- backgroundColor: yellow[100],
18
- border: `dashed 0px ${yellow[700]}`,
19
- // we need this for nested tokenized elements like paragraphs, where p is inside span
20
- '& *': {
21
- cursor: 'pointer',
22
- backgroundColor: yellow[100],
23
- border: `dashed 0px ${yellow[700]}`,
24
- },
25
- },
26
- '&.correct': {
27
- backgroundColor: green[500],
28
- '& *': {
29
- backgroundColor: green[500],
30
- },
31
- },
32
- }));
33
-
34
- export const Text = ({ text, predefined, onClick, correct }) => {
35
- const formattedText = (text || '').replace(/\n/g, '<br>');
36
-
37
- if (predefined) {
38
- const className = classNames('predefined', correct && 'correct');
39
-
40
- return <StyledText onClick={onClick} className={className} dangerouslySetInnerHTML={{ __html: formattedText }} />;
41
- } else {
42
- return <span dangerouslySetInnerHTML={{ __html: formattedText }} />;
43
- }
44
- };
45
-
46
- const notAllowedCharacters = ['\n', ' ', '\t'];
47
-
48
- export default class TokenText extends React.Component {
49
- static propTypes = {
50
- text: PropTypes.string.isRequired,
51
- tokens: PropTypes.array.isRequired,
52
- onTokenClick: PropTypes.func.isRequired,
53
- onSelectToken: PropTypes.func.isRequired,
54
- className: PropTypes.string,
55
- };
56
-
57
- /*
58
- Change this to onClick instead of mouseUp because previously, in some cases
59
- the onClick event from the <Text /> component was called right after the user
60
- selected token and that token was then removed because the setCorrectMode was not true.
61
-
62
- const { setCorrectMode } = this.state;
63
-
64
- if (setCorrectMode) {
65
- this.setCorrect(token);
66
- } else {
67
- this.removeToken(token);
68
- }
69
- */
70
- onClick = (event) => {
71
- const { onSelectToken, text, tokens } = this.props;
72
-
73
- event.preventDefault();
74
-
75
- if (typeof window === 'undefined') {
76
- return;
77
- }
78
-
79
- const selection = window.getSelection();
80
- const textSelected = selection.toString();
81
-
82
- if (textSelected.length > 0 && notAllowedCharacters.indexOf(textSelected) < 0) {
83
- if (this.root) {
84
- let offset = getCaretCharacterOffsetWithin(this.root);
85
- /*
86
- Since we implemented new line functionality (\n) using <br /> dom elements
87
- and window.getSelection is not taking that into consideration, the offset might
88
- be off by a few characters.
89
-
90
- To combat that, we check if the selected text is right at the beginning of the offset.
91
-
92
- If it's not, we add the additional offset in order for that to be accurate
93
- */
94
- const newLineOffset = text.slice(offset).indexOf(textSelected);
95
-
96
- offset += newLineOffset;
97
-
98
- if (offset !== undefined) {
99
- const endIndex = offset + textSelected.length;
100
-
101
- if (endIndex <= text.length) {
102
- const i = intersection({ start: offset, end: endIndex }, tokens);
103
- if (i.hasOverlap) {
104
- log('hasOverlap - do nothing');
105
- clearSelection();
106
- } else {
107
- const tokensToRemove = i.surroundedTokens;
108
- const token = {
109
- text: textSelected,
110
- start: offset,
111
- end: endIndex,
112
- };
113
-
114
- onSelectToken(token, tokensToRemove);
115
- clearSelection();
116
- }
117
- }
118
- }
119
- }
120
- }
121
- };
122
-
123
- render() {
124
- const { text, tokens, className, onTokenClick } = this.props;
125
- const normalized = normalize(text, tokens);
126
-
127
- return (
128
- <div className={className} ref={(r) => (this.root = r)} onClick={this.onClick}>
129
- {normalized.map((t, index) => {
130
- return <Text key={index} {...t} onClick={() => onTokenClick(t)} />;
131
- })}
132
- </div>
133
- );
134
- }
135
- }
package/src/utils.js DELETED
@@ -1,56 +0,0 @@
1
- const createElementFromHTML = (htmlString = '') => {
2
- const div = document.createElement('div');
3
-
4
- div.innerHTML = htmlString.trim();
5
-
6
- return div;
7
- };
8
-
9
- export const parseBrs = (dom) => {
10
- const brs = dom.querySelectorAll('br');
11
-
12
- brs.forEach((br) => br.replaceWith('\n'));
13
-
14
- dom.innerHTML = dom.innerHTML.replace(/\n\n/g, '\n');
15
- };
16
-
17
- export const parseParagraph = (paragraph, end) => {
18
- if (end) {
19
- return paragraph.innerHTML;
20
- }
21
-
22
- return `${paragraph.innerHTML}\n\n`;
23
- };
24
-
25
- export const parseParagraphs = (dom) => {
26
- const paragraphs = dom.querySelectorAll('p');
27
- // separate variable for easily debugging, if needed
28
- let str = '';
29
-
30
- paragraphs.forEach((par, index) => {
31
- str += parseParagraph(par, index === paragraphs.length - 1);
32
- });
33
-
34
- return str || null;
35
- };
36
-
37
- export const prepareText = (text) => {
38
- let txtDom = createElementFromHTML(text);
39
- const allDomElements = Array.from(txtDom.querySelectorAll('*'));
40
-
41
- if (txtDom.querySelectorAll('p').length === 0) {
42
- const div = document.createElement('div');
43
-
44
- div.innerHTML = `<p>${txtDom.innerHTML}</p>`;
45
- txtDom = div;
46
- }
47
-
48
- // if no dom elements, we just return the text
49
- if (allDomElements.length === 0) {
50
- return text;
51
- }
52
-
53
- parseBrs(txtDom);
54
-
55
- return parseParagraphs(txtDom);
56
- };