@expensify/react-native-live-markdown 0.1.299 → 0.1.301
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 +1 -1
- package/lib/commonjs/MarkdownTextInput.web.js.map +1 -1
- package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -1
- package/lib/commonjs/parseExpensiMark.js +9 -3
- package/lib/commonjs/parseExpensiMark.js.map +1 -1
- package/lib/commonjs/styleUtils.js +15 -2
- package/lib/commonjs/styleUtils.js.map +1 -1
- package/lib/commonjs/web/MarkdownTextInput.css +32 -5
- package/lib/commonjs/web/utils/blockUtils.js +60 -5
- package/lib/commonjs/web/utils/blockUtils.js.map +1 -1
- package/lib/commonjs/web/utils/cursorUtils.js +28 -3
- package/lib/commonjs/web/utils/cursorUtils.js.map +1 -1
- package/lib/commonjs/web/utils/inputUtils.js +49 -11
- package/lib/commonjs/web/utils/inputUtils.js.map +1 -1
- package/lib/module/MarkdownTextInput.web.js +1 -1
- package/lib/module/MarkdownTextInput.web.js.map +1 -1
- package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -1
- package/lib/module/parseExpensiMark.js +9 -3
- package/lib/module/parseExpensiMark.js.map +1 -1
- package/lib/module/styleUtils.js +15 -2
- package/lib/module/styleUtils.js.map +1 -1
- package/lib/module/web/MarkdownTextInput.css +32 -5
- package/lib/module/web/utils/blockUtils.js +58 -6
- package/lib/module/web/utils/blockUtils.js.map +1 -1
- package/lib/module/web/utils/cursorUtils.js +28 -3
- package/lib/module/web/utils/cursorUtils.js.map +1 -1
- package/lib/module/web/utils/inputUtils.js +48 -11
- package/lib/module/web/utils/inputUtils.js.map +1 -1
- package/lib/typescript/src/MarkdownTextInput.web.d.ts.map +1 -1
- package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts +16 -11
- package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/commonTypes.d.ts +1 -1
- package/lib/typescript/src/commonTypes.d.ts.map +1 -1
- package/lib/typescript/src/parseExpensiMark.d.ts.map +1 -1
- package/lib/typescript/src/styleUtils.d.ts +1 -1
- package/lib/typescript/src/styleUtils.d.ts.map +1 -1
- package/lib/typescript/src/web/utils/blockUtils.d.ts +5 -2
- package/lib/typescript/src/web/utils/blockUtils.d.ts.map +1 -1
- package/lib/typescript/src/web/utils/cursorUtils.d.ts.map +1 -1
- package/lib/typescript/src/web/utils/inputUtils.d.ts +3 -0
- package/lib/typescript/src/web/utils/inputUtils.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/MarkdownTextInput.web.tsx +3 -2
- package/src/MarkdownTextInputDecoratorViewNativeComponent.ts +17 -11
- package/src/commonTypes.ts +2 -1
- package/src/parseExpensiMark.ts +5 -3
- package/src/styleUtils.ts +14 -1
- package/src/web/MarkdownTextInput.css +32 -5
- package/src/web/utils/blockUtils.ts +74 -8
- package/src/web/utils/cursorUtils.ts +29 -3
- package/src/web/utils/inputUtils.ts +61 -12
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {MarkdownTextInputElement} from '../../MarkdownTextInput.web';
|
|
2
1
|
import type {InlineImagesInputProps, MarkdownRange} from '../../commonTypes';
|
|
2
|
+
import type {MarkdownTextInputElement} from '../../MarkdownTextInput.web';
|
|
3
|
+
import {parseStringWithUnitToNumber} from '../../styleUtils';
|
|
3
4
|
import type {PartialMarkdownStyle} from '../../styleUtils';
|
|
4
5
|
import {addInlineImagePreview} from '../inputElements/inlineImage';
|
|
5
6
|
import type {NodeType, TreeNode} from './treeUtils';
|
|
@@ -51,12 +52,9 @@ function addStyleToBlock(targetElement: HTMLElement, type: NodeType, markdownSty
|
|
|
51
52
|
});
|
|
52
53
|
break;
|
|
53
54
|
case 'code':
|
|
54
|
-
Object.assign(node.style, markdownStyle.code);
|
|
55
|
-
break;
|
|
56
55
|
case 'pre':
|
|
57
|
-
|
|
56
|
+
addCodeBlockStyles(targetElement, type, markdownStyle, isMultiline);
|
|
58
57
|
break;
|
|
59
|
-
|
|
60
58
|
case 'blockquote':
|
|
61
59
|
Object.assign(node.style, {
|
|
62
60
|
...markdownStyle.blockquote,
|
|
@@ -94,9 +92,49 @@ function addStyleToBlock(targetElement: HTMLElement, type: NodeType, markdownSty
|
|
|
94
92
|
}
|
|
95
93
|
}
|
|
96
94
|
|
|
95
|
+
function addCodeBlockStyles(targetElement: HTMLElement, type: NodeType, markdownStyle: PartialMarkdownStyle, isMultiline = true) {
|
|
96
|
+
const node = targetElement;
|
|
97
|
+
|
|
98
|
+
const defaultPrePadding = markdownStyle.pre?.padding ?? 2;
|
|
99
|
+
const preHorizontalPadding = parseStringWithUnitToNumber(markdownStyle.pre?.paddingHorizontal ?? defaultPrePadding).toString();
|
|
100
|
+
const preVerticalPadding = parseStringWithUnitToNumber(markdownStyle.pre?.paddingVertical ?? defaultPrePadding).toString();
|
|
101
|
+
|
|
102
|
+
const defaultCodePadding = markdownStyle.code?.padding ?? 0;
|
|
103
|
+
const codeHorizontalPadding = parseStringWithUnitToNumber(markdownStyle.code?.paddingHorizontal ?? defaultCodePadding).toString();
|
|
104
|
+
const codeVerticalPadding = parseStringWithUnitToNumber(markdownStyle.code?.paddingVertical ?? defaultCodePadding).toString();
|
|
105
|
+
|
|
106
|
+
switch (type) {
|
|
107
|
+
case 'code':
|
|
108
|
+
Object.assign(node.style, {
|
|
109
|
+
...markdownStyle.code,
|
|
110
|
+
fontSize: markdownStyle.code?.h1NestedFontSize && isChildOfMarkdownElement(node, 'h1') ? markdownStyle.code.h1NestedFontSize : markdownStyle.code?.fontSize,
|
|
111
|
+
padding: `${codeVerticalPadding}px ${codeHorizontalPadding}px`,
|
|
112
|
+
lineHeight: 1.5,
|
|
113
|
+
});
|
|
114
|
+
break;
|
|
115
|
+
case 'pre':
|
|
116
|
+
// In multiline style the pre block using pseudoelements, otherwise default to inline
|
|
117
|
+
if (isMultiline) {
|
|
118
|
+
Object.assign(node.style, {
|
|
119
|
+
...markdownStyle.pre,
|
|
120
|
+
padding: `${preVerticalPadding}px ${preHorizontalPadding}px`,
|
|
121
|
+
});
|
|
122
|
+
} else {
|
|
123
|
+
Object.assign(node.style, {
|
|
124
|
+
...markdownStyle.code,
|
|
125
|
+
padding: `${codeVerticalPadding}px ${codeHorizontalPadding}px`,
|
|
126
|
+
lineHeight: 1.5,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
97
135
|
const BLOCK_MARKDOWN_TYPES = ['inline-image'];
|
|
98
136
|
const FULL_LINE_MARKDOWN_TYPES = ['blockquote'];
|
|
99
|
-
const MULTILINE_MARKDOWN_TYPES = ['
|
|
137
|
+
const MULTILINE_MARKDOWN_TYPES = ['codeblock'];
|
|
100
138
|
|
|
101
139
|
function isBlockMarkdownType(type: NodeType) {
|
|
102
140
|
return BLOCK_MARKDOWN_TYPES.includes(type);
|
|
@@ -108,7 +146,7 @@ function isMultilineMarkdownType(type: NodeType) {
|
|
|
108
146
|
|
|
109
147
|
function getFirstBlockMarkdownRange(ranges: MarkdownRange[]) {
|
|
110
148
|
const blockMarkdownRange = ranges.find((r) => isBlockMarkdownType(r.type) || FULL_LINE_MARKDOWN_TYPES.includes(r.type));
|
|
111
|
-
return FULL_LINE_MARKDOWN_TYPES.includes(blockMarkdownRange
|
|
149
|
+
return blockMarkdownRange && FULL_LINE_MARKDOWN_TYPES.includes(blockMarkdownRange.type) ? undefined : blockMarkdownRange;
|
|
112
150
|
}
|
|
113
151
|
|
|
114
152
|
function extendBlockStructure(
|
|
@@ -130,4 +168,32 @@ function extendBlockStructure(
|
|
|
130
168
|
return targetNode;
|
|
131
169
|
}
|
|
132
170
|
|
|
133
|
-
|
|
171
|
+
function isDescendantOfMarkdownElement(node: HTMLElement, predicate: (type: string | null) => boolean): boolean {
|
|
172
|
+
let currentNode = node.parentNode;
|
|
173
|
+
while (currentNode && (currentNode as HTMLElement)?.contentEditable !== 'true') {
|
|
174
|
+
const elementType = (currentNode as HTMLElement).getAttribute?.('data-type');
|
|
175
|
+
if (predicate(elementType)) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
currentNode = currentNode.parentNode;
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function isChildOfMarkdownElement(node: HTMLElement, elementType: NodeType): boolean {
|
|
184
|
+
return isDescendantOfMarkdownElement(node, (type) => type === elementType);
|
|
185
|
+
}
|
|
186
|
+
function isChildOfMultilineMarkdownElement(node: HTMLElement): boolean {
|
|
187
|
+
return isDescendantOfMarkdownElement(node, (type) => MULTILINE_MARKDOWN_TYPES.includes(type as NodeType));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export {
|
|
191
|
+
addStyleToBlock,
|
|
192
|
+
extendBlockStructure,
|
|
193
|
+
isBlockMarkdownType,
|
|
194
|
+
isMultilineMarkdownType,
|
|
195
|
+
getFirstBlockMarkdownRange,
|
|
196
|
+
isChildOfMarkdownElement,
|
|
197
|
+
isChildOfMultilineMarkdownElement,
|
|
198
|
+
MULTILINE_MARKDOWN_TYPES,
|
|
199
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {MarkdownTextInputElement} from '../../MarkdownTextInput.web';
|
|
2
|
+
import {isChildOfMultilineMarkdownElement} from './blockUtils';
|
|
2
3
|
import {findHTMLElementInTree, getTreeNodeByIndex} from './treeUtils';
|
|
3
4
|
import type {TreeNode} from './treeUtils';
|
|
4
5
|
|
|
@@ -53,10 +54,35 @@ function setCursorPosition(target: MarkdownTextInputElement, startIndex: number,
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
function scrollIntoView(target: MarkdownTextInputElement, node: TreeNode) {
|
|
57
|
+
let scrollTargetElement = node.element;
|
|
56
58
|
const targetElement = target;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
|
|
60
|
+
if (!isChildOfMultilineMarkdownElement(node.element)) {
|
|
61
|
+
const orderIndex = Number(node.orderIndex.split(',')[0]);
|
|
62
|
+
const currentLine = target.tree.childNodes[orderIndex]?.element;
|
|
63
|
+
if (currentLine) {
|
|
64
|
+
scrollTargetElement = currentLine;
|
|
65
|
+
}
|
|
66
|
+
} else if (node.element.nodeName === 'BR') {
|
|
67
|
+
// Force scrolling BR into view
|
|
68
|
+
const selection = window.getSelection();
|
|
69
|
+
if (selection) {
|
|
70
|
+
const range = document.createRange();
|
|
71
|
+
|
|
72
|
+
range.setStartBefore(node.element);
|
|
73
|
+
range.collapse(true);
|
|
74
|
+
|
|
75
|
+
selection.addRange(range);
|
|
76
|
+
|
|
77
|
+
// Scroll to caret
|
|
78
|
+
const span = document.createElement('span');
|
|
79
|
+
span.textContent = '\u200B'; // zero-width space
|
|
80
|
+
range.insertNode(span);
|
|
81
|
+
span.scrollIntoView({block: 'center'});
|
|
82
|
+
span.remove(); // cleanup
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
60
86
|
|
|
61
87
|
const caretRect = scrollTargetElement.getBoundingClientRect();
|
|
62
88
|
const targetRect = target.getBoundingClientRect();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type {CSSProperties} from 'react';
|
|
2
2
|
import type {MarkdownNativeEvent, MarkdownTextInputElement} from '../../MarkdownTextInput.web';
|
|
3
|
+
import {isChildOfMarkdownElement, isChildOfMultilineMarkdownElement} from './blockUtils';
|
|
4
|
+
import BrowserUtils from './browserUtils';
|
|
3
5
|
|
|
4
6
|
const ZERO_WIDTH_SPACE = '\u200B';
|
|
5
7
|
|
|
@@ -36,17 +38,33 @@ function normalizeValue(value: string) {
|
|
|
36
38
|
return value.replaceAll('\r\n', '\n');
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Returns the parent of a given node that is higher in the hierarchy and is of a different type than 'text', 'br' or 'line'
|
|
43
|
+
*/
|
|
44
|
+
function getTopParentNode(node: ChildNode) {
|
|
45
|
+
let currentParentNode = node.parentNode;
|
|
46
|
+
while (currentParentNode && ['text', 'br', 'line'].includes(currentParentNode.parentElement?.getAttribute('data-type') || '')) {
|
|
47
|
+
currentParentNode = currentParentNode?.parentNode || null;
|
|
48
|
+
}
|
|
49
|
+
return currentParentNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* On Firefox, when breaking one codeblock, its syntax and the <br> after it can be merged into the closing syntax of the previous codeblock.
|
|
54
|
+
*/
|
|
55
|
+
function didTwoCodeblocksMerge(node: ChildNode | null) {
|
|
56
|
+
if (!node || !BrowserUtils.isFirefox) {
|
|
57
|
+
return;
|
|
48
58
|
}
|
|
59
|
+
// To identify that two codeblock has merged, we check if current line ends with <br> tag, that previously was second codeblock's opening syntax line break
|
|
60
|
+
const hasPartOfBrokenCodeblock = node.lastChild?.lastChild?.lastChild?.lastChild?.nodeName === 'BR';
|
|
61
|
+
return BrowserUtils.isFirefox && (node.lastChild as HTMLElement)?.getAttribute('data-type') === 'codeblock' && hasPartOfBrokenCodeblock;
|
|
62
|
+
}
|
|
49
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Parses the HTML structure of a MarkdownTextInputElement to a plain text string. Used for getting the correct value of the input element.
|
|
66
|
+
*/
|
|
67
|
+
function parseInnerHTMLToText(target: MarkdownTextInputElement, cursorPosition: number, inputType?: string, isMultiline = true): string {
|
|
50
68
|
const stack: ChildNode[] = [target];
|
|
51
69
|
let text = '';
|
|
52
70
|
let shouldAddNewline = false;
|
|
@@ -57,7 +75,7 @@ function parseInnerHTMLToText(target: MarkdownTextInputElement, cursorPosition:
|
|
|
57
75
|
}
|
|
58
76
|
|
|
59
77
|
while (stack.length > 0) {
|
|
60
|
-
const node = stack.pop();
|
|
78
|
+
const node = stack.pop() as HTMLElement;
|
|
61
79
|
if (!node) {
|
|
62
80
|
break;
|
|
63
81
|
}
|
|
@@ -72,7 +90,7 @@ function parseInnerHTMLToText(target: MarkdownTextInputElement, cursorPosition:
|
|
|
72
90
|
} else {
|
|
73
91
|
const firstChild = node.firstChild as HTMLElement;
|
|
74
92
|
const containsEmptyBlockElement = firstChild?.getAttribute?.('data-type') === 'block' && firstChild.textContent === '';
|
|
75
|
-
if (shouldAddNewline && !containsEmptyBlockElement) {
|
|
93
|
+
if (firstChild && shouldAddNewline && !containsEmptyBlockElement && !didTwoCodeblocksMerge(node.previousSibling)) {
|
|
76
94
|
text += '\n';
|
|
77
95
|
shouldAddNewline = false;
|
|
78
96
|
}
|
|
@@ -81,11 +99,42 @@ function parseInnerHTMLToText(target: MarkdownTextInputElement, cursorPosition:
|
|
|
81
99
|
}
|
|
82
100
|
|
|
83
101
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
102
|
+
let hasAddedNewline = false;
|
|
103
|
+
// Fix for codeblocks: Removing last codeblock newline, moves codeblock syntax too far into the codeblock content
|
|
104
|
+
// skipping one <br> after the codeblock syntax. We need to force parsing it before the text node is added.
|
|
105
|
+
if (node.parentElement && !node.parentElement.getAttribute?.('data-type') && isChildOfMarkdownElement(node, 'pre')) {
|
|
106
|
+
text += '\n';
|
|
107
|
+
const nextBR = node.parentElement?.nextElementSibling?.firstElementChild ?? node.parentElement?.nextElementSibling;
|
|
108
|
+
if (nextBR && nextBR.tagName === 'BR') {
|
|
109
|
+
nextBR.remove();
|
|
110
|
+
}
|
|
111
|
+
hasAddedNewline = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
84
114
|
// Parse text nodes into text
|
|
85
115
|
text += node.textContent;
|
|
116
|
+
|
|
117
|
+
// Fix for codeblocks: If we are adding text at the end of a multiline markdown type element, we need to add a newline
|
|
118
|
+
// because the new text can replace the last <br> element and it will not be added to the text.
|
|
119
|
+
if (
|
|
120
|
+
node.parentElement &&
|
|
121
|
+
node.parentNode?.parentElement?.nextSibling &&
|
|
122
|
+
!node.parentNode?.nextSibling &&
|
|
123
|
+
isChildOfMultilineMarkdownElement(node) &&
|
|
124
|
+
((!hasAddedNewline && isChildOfMarkdownElement(node, 'br')) || (!node.parentElement.getAttribute?.('data-type') && isChildOfMarkdownElement(node, 'syntax')))
|
|
125
|
+
) {
|
|
126
|
+
text += '\n';
|
|
127
|
+
}
|
|
86
128
|
} else if (node.nodeName === 'BR') {
|
|
87
129
|
const parentNode = getTopParentNode(node);
|
|
88
|
-
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
(isMultiline &&
|
|
133
|
+
parentNode &&
|
|
134
|
+
parentNode.parentElement?.contentEditable !== 'true' &&
|
|
135
|
+
!!((node as HTMLElement).getAttribute('data-id') || (node.parentElement as HTMLElement).getAttribute('data-type') === 'br')) ||
|
|
136
|
+
(node.parentElement?.getAttribute('data-type') === 'text' && isChildOfMultilineMarkdownElement(node))
|
|
137
|
+
) {
|
|
89
138
|
// Parse br elements into newlines only if their parent is not a child of the MarkdownTextInputElement (a paragraph when writing or a div when pasting).
|
|
90
139
|
// It prevents adding extra newlines when entering text - and now only for multiline inputs
|
|
91
140
|
text += '\n';
|