@expensify/react-native-live-markdown 0.1.70 → 0.1.72
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.
- package/lib/commonjs/MarkdownTextInput.web.js +23 -17
- package/lib/commonjs/MarkdownTextInput.web.js.map +1 -1
- package/lib/commonjs/web/InputHistory.js +50 -33
- package/lib/commonjs/web/InputHistory.js.map +1 -1
- package/lib/module/MarkdownTextInput.web.js +23 -17
- package/lib/module/MarkdownTextInput.web.js.map +1 -1
- package/lib/module/web/InputHistory.js +50 -33
- package/lib/module/web/InputHistory.js.map +1 -1
- package/lib/parser/react-native-live-markdown-parser.js +15 -15
- package/lib/typescript/src/MarkdownTextInput.web.d.ts.map +1 -1
- package/lib/typescript/src/web/InputHistory.d.ts +4 -3
- package/lib/typescript/src/web/InputHistory.d.ts.map +1 -1
- package/package.json +1 -1
- package/parser/react-native-live-markdown-parser.js +15 -15
- package/src/MarkdownTextInput.web.tsx +23 -18
- package/src/web/InputHistory.ts +55 -36
|
@@ -80,6 +80,10 @@ let focusTimeout: NodeJS.Timeout | null = null;
|
|
|
80
80
|
function normalizeValue(value: string) {
|
|
81
81
|
return value.replace(/\n$/, '');
|
|
82
82
|
}
|
|
83
|
+
// Adds one '\n' at the end of the string if it's missing
|
|
84
|
+
function denormalizeValue(value: string) {
|
|
85
|
+
return value.endsWith('\n') ? `${value}\n` : value;
|
|
86
|
+
}
|
|
83
87
|
|
|
84
88
|
// If an Input Method Editor is processing key input, the 'keyCode' is 229.
|
|
85
89
|
// https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode
|
|
@@ -174,7 +178,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
174
178
|
const dimensions = React.useRef<Dimensions | null>(null);
|
|
175
179
|
|
|
176
180
|
if (!history.current) {
|
|
177
|
-
history.current = new InputHistory(100);
|
|
181
|
+
history.current = new InputHistory(100, 150, value || '');
|
|
178
182
|
}
|
|
179
183
|
|
|
180
184
|
const flattenedStyle = useMemo(() => StyleSheet.flatten(style), [style]);
|
|
@@ -203,7 +207,8 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
203
207
|
}
|
|
204
208
|
const parsedText = ParseUtils.parseText(target, text, cursorPosition, customMarkdownStyles, !multiline);
|
|
205
209
|
if (history.current && shouldAddToHistory) {
|
|
206
|
-
history
|
|
210
|
+
// We need to normalize the value before saving it to the history to prevent situations when additional new lines break the cursor position calculation logic
|
|
211
|
+
history.current.throttledAdd(normalizeValue(parsedText.text), parsedText.cursorPosition);
|
|
207
212
|
}
|
|
208
213
|
|
|
209
214
|
return parsedText;
|
|
@@ -236,7 +241,8 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
236
241
|
(target: HTMLDivElement) => {
|
|
237
242
|
if (!history.current) return '';
|
|
238
243
|
const item = history.current.undo();
|
|
239
|
-
|
|
244
|
+
const undoValue = item ? denormalizeValue(item.text) : null;
|
|
245
|
+
return parseText(target, undoValue, processedMarkdownStyle, item ? item.cursorPosition : null, false).text;
|
|
240
246
|
},
|
|
241
247
|
[parseText, processedMarkdownStyle],
|
|
242
248
|
);
|
|
@@ -245,7 +251,8 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
245
251
|
(target: HTMLDivElement) => {
|
|
246
252
|
if (!history.current) return '';
|
|
247
253
|
const item = history.current.redo();
|
|
248
|
-
|
|
254
|
+
const redoValue = item ? denormalizeValue(item.text) : null;
|
|
255
|
+
return parseText(target, redoValue, processedMarkdownStyle, item ? item.cursorPosition : null, false).text;
|
|
249
256
|
},
|
|
250
257
|
[parseText, processedMarkdownStyle],
|
|
251
258
|
);
|
|
@@ -328,9 +335,10 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
328
335
|
if (!divRef.current || !(e.target instanceof HTMLElement)) {
|
|
329
336
|
return;
|
|
330
337
|
}
|
|
338
|
+
const changedText = e.target.innerText;
|
|
331
339
|
|
|
332
340
|
if (compositionRef.current) {
|
|
333
|
-
updateTextColor(divRef.current,
|
|
341
|
+
updateTextColor(divRef.current, changedText);
|
|
334
342
|
compositionRef.current = false;
|
|
335
343
|
return;
|
|
336
344
|
}
|
|
@@ -344,14 +352,22 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
344
352
|
case 'historyRedo':
|
|
345
353
|
text = redo(divRef.current);
|
|
346
354
|
break;
|
|
355
|
+
case 'insertFromPaste':
|
|
356
|
+
// if there is no newline at the end of the copied text, contentEditable adds invisible <br> tag at the end of the text, so we need to normalize it
|
|
357
|
+
if (changedText.length > 2 && changedText[changedText.length - 2] !== '\n' && changedText[changedText.length - 1] === '\n') {
|
|
358
|
+
text = parseText(divRef.current, normalizeValue(changedText), processedMarkdownStyle).text;
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
text = parseText(divRef.current, changedText, processedMarkdownStyle).text;
|
|
362
|
+
break;
|
|
347
363
|
default:
|
|
348
|
-
text = parseText(divRef.current,
|
|
364
|
+
text = parseText(divRef.current, changedText, processedMarkdownStyle).text;
|
|
349
365
|
}
|
|
350
366
|
if (pasteRef?.current) {
|
|
351
367
|
pasteRef.current = false;
|
|
352
368
|
updateSelection(e);
|
|
353
369
|
}
|
|
354
|
-
updateTextColor(divRef.current,
|
|
370
|
+
updateTextColor(divRef.current, text);
|
|
355
371
|
|
|
356
372
|
if (onChange) {
|
|
357
373
|
const event = e as unknown as NativeSyntheticEvent<any>;
|
|
@@ -587,17 +603,6 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
|
|
|
587
603
|
CursorUtils.setCursorPosition(divRef.current, newSelection.start, newSelection.end);
|
|
588
604
|
}, [selection, updateRefSelectionVariables]);
|
|
589
605
|
|
|
590
|
-
useEffect(() => {
|
|
591
|
-
if (history.current?.history.length !== 0) {
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
const currentValue = value ?? '';
|
|
595
|
-
history.current.add(currentValue, currentValue.length);
|
|
596
|
-
|
|
597
|
-
handleContentSizeChange();
|
|
598
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
599
|
-
}, []);
|
|
600
|
-
|
|
601
606
|
return (
|
|
602
607
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
603
608
|
<div
|
package/src/web/InputHistory.ts
CHANGED
|
@@ -6,7 +6,7 @@ type HistoryItem = {
|
|
|
6
6
|
export default class InputHistory {
|
|
7
7
|
depth: number;
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
items: HistoryItem[];
|
|
10
10
|
|
|
11
11
|
historyIndex: number;
|
|
12
12
|
|
|
@@ -16,19 +16,19 @@ export default class InputHistory {
|
|
|
16
16
|
|
|
17
17
|
debounceTime: number;
|
|
18
18
|
|
|
19
|
-
constructor(depth: number, debounceTime = 150) {
|
|
19
|
+
constructor(depth: number, debounceTime = 150, startingText = '') {
|
|
20
20
|
this.depth = depth;
|
|
21
|
-
this.
|
|
21
|
+
this.items = [{text: startingText, cursorPosition: startingText.length}];
|
|
22
22
|
this.historyIndex = 0;
|
|
23
23
|
this.debounceTime = debounceTime;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
getCurrentItem(): HistoryItem | null {
|
|
27
|
-
return this.
|
|
27
|
+
return this.items[this.historyIndex] || null;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
setHistory(newHistory: HistoryItem[]): void {
|
|
31
|
-
this.
|
|
31
|
+
this.items = newHistory.slice(newHistory.length - this.depth);
|
|
32
32
|
this.historyIndex = newHistory.length - 1;
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -37,80 +37,99 @@ export default class InputHistory {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
clear(): void {
|
|
40
|
-
this.
|
|
40
|
+
this.items = [];
|
|
41
41
|
this.historyIndex = 0;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
this.currentText = text;
|
|
46
|
-
|
|
44
|
+
throttledAdd(text: string, cursorPosition: number): void {
|
|
47
45
|
if (this.timeout) {
|
|
48
46
|
clearTimeout(this.timeout);
|
|
49
47
|
}
|
|
50
48
|
|
|
49
|
+
if (this.currentText === null) {
|
|
50
|
+
this.timeout = null;
|
|
51
|
+
this.add(text, cursorPosition);
|
|
52
|
+
} else {
|
|
53
|
+
this.items[this.historyIndex] = {text, cursorPosition};
|
|
54
|
+
}
|
|
55
|
+
this.currentText = text;
|
|
56
|
+
|
|
51
57
|
this.timeout = setTimeout(() => {
|
|
52
|
-
if (this.currentText == null) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
this.add(this.currentText, cursorPosition);
|
|
56
58
|
this.currentText = null;
|
|
57
59
|
}, this.debounceTime);
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
stopTimeout(): void {
|
|
63
|
+
if (this.timeout) {
|
|
64
|
+
clearTimeout(this.timeout);
|
|
65
|
+
this.timeout = null;
|
|
66
|
+
}
|
|
67
|
+
this.currentText = null;
|
|
68
|
+
}
|
|
69
|
+
|
|
60
70
|
add(text: string, cursorPosition: number): void {
|
|
61
|
-
if (this.
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
this.historyIndex = this.history.length - 1;
|
|
71
|
+
if (this.items.length > 0) {
|
|
72
|
+
const currentItem = this.items[this.historyIndex];
|
|
73
|
+
if (currentItem && text === currentItem.text) {
|
|
65
74
|
return;
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
77
|
|
|
69
|
-
if (this.historyIndex < this.
|
|
70
|
-
this.
|
|
78
|
+
if (this.historyIndex < this.items.length - 1) {
|
|
79
|
+
this.items.splice(this.historyIndex + 1);
|
|
80
|
+
this.historyIndex = this.items.length - 1;
|
|
71
81
|
}
|
|
72
82
|
|
|
73
|
-
this.
|
|
74
|
-
if (this.
|
|
75
|
-
this.
|
|
83
|
+
this.items.push({text, cursorPosition});
|
|
84
|
+
if (this.items.length > this.depth) {
|
|
85
|
+
this.items.shift();
|
|
86
|
+
} else {
|
|
87
|
+
this.historyIndex += 1;
|
|
76
88
|
}
|
|
77
|
-
|
|
78
|
-
this.historyIndex = this.history.length - 1;
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
undo(): HistoryItem | null {
|
|
82
|
-
|
|
83
|
-
clearTimeout(this.timeout);
|
|
84
|
-
this.timeout = null;
|
|
85
|
-
return this.history[this.historyIndex] || null;
|
|
86
|
-
}
|
|
92
|
+
this.stopTimeout();
|
|
87
93
|
|
|
88
|
-
if (this.
|
|
94
|
+
if (this.items.length === 0 || this.historyIndex - 1 < 0) {
|
|
89
95
|
return null;
|
|
90
96
|
}
|
|
91
97
|
|
|
98
|
+
const currentHistoryItem = this.items[this.historyIndex];
|
|
99
|
+
const previousHistoryItem = this.items[this.historyIndex - 1];
|
|
100
|
+
|
|
101
|
+
const undoItem = previousHistoryItem
|
|
102
|
+
? {
|
|
103
|
+
text: previousHistoryItem.text,
|
|
104
|
+
cursorPosition: Math.min(
|
|
105
|
+
(currentHistoryItem?.cursorPosition ?? 0) - ((currentHistoryItem?.text ?? '').length - (previousHistoryItem?.text ?? '').length),
|
|
106
|
+
(previousHistoryItem?.text ?? '').length,
|
|
107
|
+
),
|
|
108
|
+
}
|
|
109
|
+
: null;
|
|
110
|
+
|
|
92
111
|
if (this.historyIndex > 0) {
|
|
93
112
|
this.historyIndex -= 1;
|
|
94
113
|
}
|
|
95
|
-
|
|
114
|
+
|
|
115
|
+
return undoItem;
|
|
96
116
|
}
|
|
97
117
|
|
|
98
118
|
redo(): HistoryItem | null {
|
|
99
119
|
if (this.currentText !== null && this.timeout) {
|
|
100
|
-
|
|
101
|
-
return this.history[this.history.length - 1] || null;
|
|
120
|
+
this.stopTimeout();
|
|
102
121
|
}
|
|
103
122
|
|
|
104
|
-
if (this.
|
|
123
|
+
if (this.items.length === 0 || this.historyIndex + 1 > this.items.length) {
|
|
105
124
|
return null;
|
|
106
125
|
}
|
|
107
126
|
|
|
108
|
-
if (this.historyIndex < this.
|
|
127
|
+
if (this.historyIndex < this.items.length - 1) {
|
|
109
128
|
this.historyIndex += 1;
|
|
110
129
|
} else {
|
|
111
130
|
return null;
|
|
112
131
|
}
|
|
113
132
|
|
|
114
|
-
return this.
|
|
133
|
+
return this.items[this.historyIndex] || null;
|
|
115
134
|
}
|
|
116
135
|
}
|