@expensify/react-native-live-markdown 0.1.298 → 0.1.300
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 +2 -2
- 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 +28 -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 +51 -13
- package/lib/commonjs/web/utils/inputUtils.js.map +1 -1
- package/lib/commonjs/web/utils/parserUtils.js +6 -4
- package/lib/commonjs/web/utils/parserUtils.js.map +1 -1
- package/lib/module/MarkdownTextInput.web.js +2 -2
- 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 +28 -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 +50 -13
- package/lib/module/web/utils/inputUtils.js.map +1 -1
- package/lib/module/web/utils/parserUtils.js +6 -4
- package/lib/module/web/utils/parserUtils.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 +4 -1
- package/lib/typescript/src/web/utils/inputUtils.d.ts.map +1 -1
- package/lib/typescript/src/web/utils/parserUtils.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/MarkdownTextInput.web.tsx +4 -3
- 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 +28 -5
- package/src/web/utils/blockUtils.ts +74 -8
- package/src/web/utils/cursorUtils.ts +29 -3
- package/src/web/utils/inputUtils.ts +63 -14
- package/src/web/utils/parserUtils.ts +5 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inputUtils.d.ts","sourceRoot":"","sources":["../../../../../src/web/utils/inputUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,OAAO,CAAC;AACzC,OAAO,KAAK,EAAC,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"inputUtils.d.ts","sourceRoot":"","sources":["../../../../../src/web/utils/inputUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,OAAO,CAAC;AACzC,OAAO,KAAK,EAAC,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,6BAA6B,CAAC;AAQ/F,iBAAS,gBAAgB,CAAC,WAAW,EAAE,UAAU,CAAC,aAAa,GAAG,mBAAmB,WAEpF;AAED,iBAAS,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,UAK3D;AAED,iBAAS,gBAAgB,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,GAAG,SAAS,UAcvG;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE,MAAM,UAEpC;AAyBD;;GAEG;AACH,iBAAS,oBAAoB,CAAC,MAAM,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,WAAW,UAAO,GAAG,MAAM,CA8FtI;AAED,OAAO,EAAC,gBAAgB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,cAAc,EAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parserUtils.d.ts","sourceRoot":"","sources":["../../../../../src/web/utils/parserUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,6BAA6B,CAAC;AAE/F,OAAO,KAAK,EAAW,QAAQ,EAAC,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,kBAAkB,CAAC;AAG3D,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAI7E,KAAK,SAAS,GAAG;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,aAAa,EAAE,CAAC;CACjC,CAAC;AAoEF;;;GAGG;AACH,iBAAS,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,eAyBlE;
|
|
1
|
+
{"version":3,"file":"parserUtils.d.ts","sourceRoot":"","sources":["../../../../../src/web/utils/parserUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,6BAA6B,CAAC;AAE/F,OAAO,KAAK,EAAW,QAAQ,EAAC,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,kBAAkB,CAAC;AAG3D,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAI7E,KAAK,SAAS,GAAG;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,aAAa,EAAE,CAAC;CACjC,CAAC;AAoEF;;;GAGG;AACH,iBAAS,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,eAyBlE;AA2ED,yEAAyE;AACzE,iBAAS,sBAAsB,CAC7B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,EAAE,EACvB,WAAW,UAAO,EAClB,aAAa,GAAE,oBAAyB,EACxC,mBAAmB,UAAQ,EAC3B,YAAY,GAAE,wBAAwB,GAAG,IAAW,EACpD,iBAAiB,GAAE,sBAA2B;;;EA4G/C;AAcD,iBAAS,oBAAoB,CAC3B,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,aAAa,EAAE,EAClD,MAAM,EAAE,wBAAwB,EAChC,IAAI,EAAE,MAAM,EACZ,mBAAmB,EAAE,MAAM,GAAG,IAAI,EAClC,WAAW,UAAO,EAClB,aAAa,GAAE,oBAAyB,EACxC,wBAAwB,UAAQ,EAChC,oBAAoB,UAAQ,EAC5B,oBAAoB,UAAQ,EAC5B,iBAAiB,GAAE,sBAA2B;;;EAsC/C;AAED,OAAO,EAAC,oBAAoB,EAAE,sBAAsB,EAAE,cAAc,EAAC,CAAC;AACtE,YAAY,EAAC,SAAS,EAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expensify/react-native-live-markdown",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.300",
|
|
4
4
|
"description": "Drop-in replacement for React Native's TextInput component with Markdown formatting.",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"eslint-plugin-prettier": "^4.0.0",
|
|
88
88
|
"eslint-plugin-promise": "^6.1.1",
|
|
89
89
|
"eslint-plugin-tsdoc": "^0.2.17",
|
|
90
|
-
"expensify-common": "2.0.
|
|
90
|
+
"expensify-common": "2.0.148",
|
|
91
91
|
"jest": "^29.6.3",
|
|
92
92
|
"jest-environment-jsdom": "^29.7.0",
|
|
93
93
|
"nodemon": "^3.1.3",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"typescript": "~5.3.3"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
|
-
"expensify-common": ">=2.0.
|
|
107
|
+
"expensify-common": ">=2.0.148",
|
|
108
108
|
"react": "*",
|
|
109
109
|
"react-native": "*",
|
|
110
110
|
"react-native-reanimated": ">=3.17.0"
|
|
@@ -355,7 +355,7 @@ const MarkdownTextInput = React.forwardRef<MarkdownTextInput, MarkdownTextInputP
|
|
|
355
355
|
updateTextColor(divRef.current, e.target.textContent ?? '');
|
|
356
356
|
const previousText = divRef.current.value;
|
|
357
357
|
let parsedText = normalizeValue(
|
|
358
|
-
inputType === 'pasteText' ? pasteContent.current || '' : parseInnerHTMLToText(e.target as MarkdownTextInputElement, contentSelection.current.start, inputType),
|
|
358
|
+
inputType === 'pasteText' ? pasteContent.current || '' : parseInnerHTMLToText(e.target as MarkdownTextInputElement, contentSelection.current.start, inputType, multiline),
|
|
359
359
|
);
|
|
360
360
|
|
|
361
361
|
if (pasteContent.current) {
|
|
@@ -451,8 +451,9 @@ const MarkdownTextInput = React.forwardRef<MarkdownTextInput, MarkdownTextInputP
|
|
|
451
451
|
handleContentSizeChange();
|
|
452
452
|
},
|
|
453
453
|
[
|
|
454
|
-
parser,
|
|
455
454
|
updateTextColor,
|
|
455
|
+
multiline,
|
|
456
|
+
maxLength,
|
|
456
457
|
updateSelection,
|
|
457
458
|
onChange,
|
|
458
459
|
onChangeText,
|
|
@@ -461,9 +462,9 @@ const MarkdownTextInput = React.forwardRef<MarkdownTextInput, MarkdownTextInputP
|
|
|
461
462
|
redo,
|
|
462
463
|
handleFormatSelection,
|
|
463
464
|
parseText,
|
|
465
|
+
parser,
|
|
464
466
|
processedMarkdownStyle,
|
|
465
467
|
setEventProps,
|
|
466
|
-
maxLength,
|
|
467
468
|
],
|
|
468
469
|
);
|
|
469
470
|
|
|
@@ -3,6 +3,20 @@ import type {ColorValue, ViewProps} from 'react-native';
|
|
|
3
3
|
import type {Float, Int32} from 'react-native/Libraries/Types/CodegenTypes';
|
|
4
4
|
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
|
|
5
5
|
|
|
6
|
+
interface CodeBlockStyle {
|
|
7
|
+
fontFamily: string;
|
|
8
|
+
fontSize: Float;
|
|
9
|
+
color: ColorValue;
|
|
10
|
+
backgroundColor: ColorValue;
|
|
11
|
+
borderColor?: ColorValue;
|
|
12
|
+
borderWidth?: Float;
|
|
13
|
+
borderRadius?: Float;
|
|
14
|
+
borderStyle?: string;
|
|
15
|
+
padding?: Float;
|
|
16
|
+
paddingVertical?: Float;
|
|
17
|
+
paddingHorizontal?: Float;
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
interface MarkdownStyle {
|
|
7
21
|
syntax: {
|
|
8
22
|
color: ColorValue;
|
|
@@ -23,18 +37,10 @@ interface MarkdownStyle {
|
|
|
23
37
|
marginLeft: Float;
|
|
24
38
|
paddingLeft: Float;
|
|
25
39
|
};
|
|
26
|
-
code: {
|
|
27
|
-
|
|
28
|
-
fontSize: Float;
|
|
29
|
-
color: ColorValue;
|
|
30
|
-
backgroundColor: ColorValue;
|
|
31
|
-
};
|
|
32
|
-
pre: {
|
|
33
|
-
fontFamily: string;
|
|
34
|
-
fontSize: Float;
|
|
35
|
-
color: ColorValue;
|
|
36
|
-
backgroundColor: ColorValue;
|
|
40
|
+
code: CodeBlockStyle & {
|
|
41
|
+
h1NestedFontSize?: Float;
|
|
37
42
|
};
|
|
43
|
+
pre: CodeBlockStyle;
|
|
38
44
|
mentionHere: {
|
|
39
45
|
color: ColorValue;
|
|
40
46
|
backgroundColor: ColorValue;
|
package/src/commonTypes.ts
CHANGED
package/src/parseExpensiMark.ts
CHANGED
|
@@ -174,9 +174,11 @@ function parseTreeToTextAndRanges(tree: StackItem): [string, MarkdownRange[]] {
|
|
|
174
174
|
} else if (node.tag === '<br />') {
|
|
175
175
|
text += '\n';
|
|
176
176
|
} else if (node.tag.startsWith('<pre')) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
const [_, lb, content] = node.children.join('').match(/^(\r?\n)([\s\S]*)$/) ?? [];
|
|
178
|
+
// Adding opening ("```${lb}") and closing ("```") codeblock syntax length, equal to 7, to the content length
|
|
179
|
+
ranges.push({type: 'codeblock', start: text.length, length: (content?.length ?? 0) + 7});
|
|
180
|
+
appendSyntax(`\`\`\`${lb}`);
|
|
181
|
+
addChildrenWithStyle(`${content}`, 'pre');
|
|
180
182
|
appendSyntax('```');
|
|
181
183
|
} else if (node.tag.startsWith('<a href="')) {
|
|
182
184
|
const rawHref = node.tag.match(/href="([^"]*)"/)![1]!; // always present
|
package/src/styleUtils.ts
CHANGED
|
@@ -42,12 +42,22 @@ function makeDefaultMarkdownStyle(): MarkdownStyle {
|
|
|
42
42
|
fontSize: 20,
|
|
43
43
|
color: 'black',
|
|
44
44
|
backgroundColor: 'lightgray',
|
|
45
|
+
borderColor: 'gray',
|
|
46
|
+
borderWidth: 1,
|
|
47
|
+
borderRadius: 4,
|
|
48
|
+
borderStyle: 'solid',
|
|
49
|
+
padding: 0,
|
|
45
50
|
},
|
|
46
51
|
pre: {
|
|
47
52
|
fontFamily: FONT_FAMILY_MONOSPACE,
|
|
48
53
|
fontSize: 20,
|
|
49
54
|
color: 'black',
|
|
50
55
|
backgroundColor: 'lightgray',
|
|
56
|
+
borderColor: 'gray',
|
|
57
|
+
borderWidth: 1,
|
|
58
|
+
borderRadius: 4,
|
|
59
|
+
borderStyle: 'solid',
|
|
60
|
+
padding: 2,
|
|
51
61
|
},
|
|
52
62
|
mentionHere: {
|
|
53
63
|
color: 'green',
|
|
@@ -96,7 +106,10 @@ function mergeMarkdownStyleWithDefault(input: PartialMarkdownStyle | undefined):
|
|
|
96
106
|
return output;
|
|
97
107
|
}
|
|
98
108
|
|
|
99
|
-
function parseStringWithUnitToNumber(value: string | null): number {
|
|
109
|
+
function parseStringWithUnitToNumber(value: string | number | null): number {
|
|
110
|
+
if (typeof value === 'number') {
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
100
113
|
return value ? parseInt(value.replace('px', ''), 10) : 0;
|
|
101
114
|
}
|
|
102
115
|
|
|
@@ -30,11 +30,34 @@
|
|
|
30
30
|
padding-right: 1px !important;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
.react-native-live-markdown-input-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
.react-native-live-markdown-input-multiline *[data-type='pre'] {
|
|
34
|
+
line-height: 1.3;
|
|
35
|
+
display: block;
|
|
36
|
+
width: fit-content;
|
|
37
|
+
max-width: 100%;
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.react-native-live-markdown-input-multiline *[data-type='codeblock'] {
|
|
42
|
+
position: relative;
|
|
43
|
+
width: fit-content;
|
|
44
|
+
max-width: 100%;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
z-index: 2;
|
|
47
|
+
display: block;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.react-native-live-markdown-input-singleline *[data-type='code'] > *[data-type='emoji'],
|
|
51
|
+
.react-native-live-markdown-input-multiline *[data-type='code'] > *[data-type='emoji'] {
|
|
52
|
+
font-size: inherit !important;
|
|
53
|
+
line-height: inherit !important;
|
|
54
|
+
vertical-align: unset !important;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.react-native-live-markdown-input-multiline *[data-type='line'] *[data-type='syntax']:has(+ *[data-type='pre']),
|
|
58
|
+
.react-native-live-markdown-input-multiline *[data-type='line'] *[data-type='pre'] + *[data-type='syntax'] {
|
|
59
|
+
display: block;
|
|
60
|
+
line-height: 1.3;
|
|
38
61
|
}
|
|
39
62
|
|
|
40
63
|
@keyframes react-native-live-markdown-spin {
|
|
@@ -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
|
-
return currentParentNode;
|
|
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
48
|
}
|
|
49
|
+
return currentParentNode;
|
|
50
|
+
}
|
|
49
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;
|
|
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
|
+
}
|
|
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
|
}
|
|
@@ -67,12 +85,12 @@ function parseInnerHTMLToText(target: MarkdownTextInputElement, cursorPosition:
|
|
|
67
85
|
if (isTopComponent) {
|
|
68
86
|
// When inputType is undefined, the first part of the replaced text is added as a text node.
|
|
69
87
|
// Because of it, we need to prevent adding new lines in this case
|
|
70
|
-
if (!inputType && node.nodeType === Node.TEXT_NODE) {
|
|
88
|
+
if (!isMultiline || (!inputType && node.nodeType === Node.TEXT_NODE)) {
|
|
71
89
|
shouldAddNewline = false;
|
|
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,13 +99,44 @@ 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
|
-
// It prevents adding extra newlines when entering text
|
|
139
|
+
// It prevents adding extra newlines when entering text - and now only for multiline inputs
|
|
91
140
|
text += '\n';
|
|
92
141
|
}
|
|
93
142
|
} else {
|
|
@@ -152,7 +152,8 @@ function addTextToElement(node: TreeNode, text: string, isMultiline = true) {
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
// Only add BR elements for multiline inputs or when there are actual line breaks
|
|
156
|
+
if (isMultiline && (index < lines.length - 1 || (index === 0 && line === ''))) {
|
|
156
157
|
addBrElement(node);
|
|
157
158
|
}
|
|
158
159
|
});
|
|
@@ -228,7 +229,7 @@ function parseRangesToHTMLNodes(
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
if (line.markdownRanges.length === 0) {
|
|
231
|
-
addTextToElement(currentParentNode, line.text);
|
|
232
|
+
addTextToElement(currentParentNode, line.text, isMultiline);
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
let wasBlockGenerated = false;
|
|
@@ -254,7 +255,7 @@ function parseRangesToHTMLNodes(
|
|
|
254
255
|
// add text before the markdown range
|
|
255
256
|
const textBeforeRange = line.text.substring(lastRangeEndIndex - line.start, range.start - line.start);
|
|
256
257
|
if (textBeforeRange) {
|
|
257
|
-
addTextToElement(currentParentNode, textBeforeRange);
|
|
258
|
+
addTextToElement(currentParentNode, textBeforeRange, isMultiline);
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
// create markdown span element
|
|
@@ -284,7 +285,7 @@ function parseRangesToHTMLNodes(
|
|
|
284
285
|
while (currentParentNode.parentNode !== null && nextRangeStartIndex >= currentParentNode.start + currentParentNode.length) {
|
|
285
286
|
const textAfterRange = line.text.substring(lastRangeEndIndex - line.start, currentParentNode.start - line.start + currentParentNode.length);
|
|
286
287
|
if (textAfterRange) {
|
|
287
|
-
addTextToElement(currentParentNode, textAfterRange);
|
|
288
|
+
addTextToElement(currentParentNode, textAfterRange, isMultiline);
|
|
288
289
|
}
|
|
289
290
|
lastRangeEndIndex = currentParentNode.start + currentParentNode.length;
|
|
290
291
|
if (currentParentNode.parentNode.type !== 'root') {
|