Skip to content

Commit

Permalink
Merge branch 'main' into charles
Browse files Browse the repository at this point in the history
  • Loading branch information
charles-liang authored May 16, 2024
2 parents 2627931 + 2030589 commit 6dd6e8a
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 185 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@expensify/react-native-live-markdown",
"version": "0.1.70",
"version": "0.1.72",
"description": "Drop-in replacement for React Native's TextInput component with Markdown formatting.",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down
24 changes: 4 additions & 20 deletions parser/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,6 @@ describe('blockquote', () => {
{type: 'syntax', start: 0, length: 1},
]);
});

test('without space', () => {
expect('>Hello world!').toBeParsedAs([
{type: 'blockquote', start: 0, length: 13},
{type: 'syntax', start: 0, length: 1},
]);
});
});

test('multiple blockquotes', () => {
Expand Down Expand Up @@ -246,15 +239,6 @@ test('nested bold and italic', () => {
});

describe('nested h1 in blockquote', () => {
test('without spaces', () => {
expect('># Hello world').toBeParsedAs([
{type: 'blockquote', start: 0, length: 14},
{type: 'syntax', start: 0, length: 1},
{type: 'syntax', start: 1, length: 2},
{type: 'h1', start: 3, length: 11},
]);
});

test('with single space', () => {
expect('> # Hello world').toBeParsedAs([
{type: 'blockquote', start: 0, length: 15},
Expand All @@ -265,11 +249,11 @@ describe('nested h1 in blockquote', () => {
});

test('with multiple spaces after #', () => {
expect('># Hello world').toBeParsedAs([
{type: 'blockquote', start: 0, length: 17},
expect('> # Hello world').toBeParsedAs([
{type: 'blockquote', start: 0, length: 18},
{type: 'syntax', start: 0, length: 1},
{type: 'syntax', start: 1, length: 2},
{type: 'h1', start: 3, length: 14},
{type: 'syntax', start: 2, length: 2},
{type: 'h1', start: 4, length: 14},
]);
});
});
Expand Down
2 changes: 1 addition & 1 deletion parser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"typescript": "^5.3.3"
},
"dependencies": {
"expensify-common": "Expensify/expensify-common#1713f28214f0e7176c4fd13433fb0ea15491ebf9",
"expensify-common": "Expensify/expensify-common#a018512b1e0fce4d4b5a61c00ca826b8887007ad",
"patch-package": "^8.0.0",
"underscore": "^1.13.6"
}
Expand Down
51 changes: 26 additions & 25 deletions parser/patches/expensify-common+1.0.0.patch
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
diff --git a/node_modules/expensify-common/lib/CONST.jsx b/node_modules/expensify-common/lib/CONST.jsx
index 49cebfb..a086055 100644
index b68261b..99bd3e3 100644
--- a/node_modules/expensify-common/lib/CONST.jsx
+++ b/node_modules/expensify-common/lib/CONST.jsx
@@ -361,14 +361,14 @@ export const CONST = {
@@ -361,7 +361,7 @@ export const CONST = {
*
* @type RegExp
*/
Expand All @@ -11,16 +11,17 @@ index 49cebfb..a086055 100644

/**
* Regex matching an text containing an Emoji that can be a single emoji or made up by some different emojis
*
@@ -369,7 +369,7 @@ export const CONST = {
* @type RegExp
*/
- EMOJI_RULE: /[\p{Extended_Pictographic}](\u200D[\p{Extended_Pictographic}]|[\u{1F3FB}-\u{1F3FF}]|[\u{E0020}-\u{E007F}]|\uFE0F|\u20E3)*|[\u{1F1E6}-\u{1F1FF}]{2}|[#*0-9]\uFE0F?\u20E3/gu,
+ EMOJI_RULE: /(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u2388\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2605\u2607-\u2612\u2614-\u2685\u2690-\u2705\u2708-\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763-\u2767\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC00-\uDCFF\uDD0D-\uDD0F\uDD2F\uDD6C-\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAD-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDFFA]|\uD83D[\uDC00-\uDD3D\uDD46-\uDE4F\uDE80-\uDEFF\uDF74-\uDF7F\uDFD5-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDEFF]|\uD83F[\uDC00-\uDFFD])(\u200D(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u2388\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2605\u2607-\u2612\u2614-\u2685\u2690-\u2705\u2708-\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763-\u2767\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC00-\uDCFF\uDD0D-\uDD0F\uDD2F\uDD6C-\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAD-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDFFA]|\uD83D[\uDC00-\uDD3D\uDD46-\uDE4F\uDE80-\uDEFF\uDF74-\uDF7F\uDFD5-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDEFF]|\uD83F[\uDC00-\uDFFD])|(?:\uD83C[\uDFFB-\uDFFF])|(?:\uDB40[\uDC20-\uDC7F])|\uFE0F|\u20E3)*|(?:\uD83C[\uDDE6-\uDDFF]){2}|[#\*0-9]\uFE0F?\u20E3/g
EMOJI_RULE:
- /[\p{Extended_Pictographic}](\u200D[\p{Extended_Pictographic}]|[\u{1F3FB}-\u{1F3FF}]|[\u{E0020}-\u{E007F}]|\uFE0F|\u20E3)*|[\u{1F1E6}-\u{1F1FF}]{2}|[#*0-9]\uFE0F?\u20E3/gu,
+ /(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u2388\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2605\u2607-\u2612\u2614-\u2685\u2690-\u2705\u2708-\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763-\u2767\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC00-\uDCFF\uDD0D-\uDD0F\uDD2F\uDD6C-\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAD-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDFFA]|\uD83D[\uDC00-\uDD3D\uDD46-\uDE4F\uDE80-\uDEFF\uDF74-\uDF7F\uDFD5-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDEFF]|\uD83F[\uDC00-\uDFFD])(\u200D(?:[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u2388\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2605\u2607-\u2612\u2614-\u2685\u2690-\u2705\u2708-\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763-\u2767\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC00-\uDCFF\uDD0D-\uDD0F\uDD2F\uDD6C-\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDAD-\uDDE5\uDE01-\uDE0F\uDE1A\uDE2F\uDE32-\uDE3A\uDE3C-\uDE3F\uDE49-\uDFFA]|\uD83D[\uDC00-\uDD3D\uDD46-\uDE4F\uDE80-\uDEFF\uDF74-\uDF7F\uDFD5-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE-\uDCFF\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDEFF]|\uD83F[\uDC00-\uDFFD])|(?:\uD83C[\uDFFB-\uDFFF])|(?:\uDB40[\uDC20-\uDC7F])|\uFE0F|\u20E3)*|(?:\uD83C[\uDDE6-\uDDFF]){2}|[#\*0-9]\uFE0F?\u20E3/g,
},

REPORT: {
diff --git a/node_modules/expensify-common/lib/ExpensiMark.js b/node_modules/expensify-common/lib/ExpensiMark.js
index 5c6a593..e3b304e 100644
index 4b8a7eb..197842d 100644
--- a/node_modules/expensify-common/lib/ExpensiMark.js
+++ b/node_modules/expensify-common/lib/ExpensiMark.js
@@ -8,8 +8,8 @@ const MARKDOWN_IMAGE_REGEX = new RegExp(`\\!(?:\\[([^\\][]*(?:\\[[^\\][]*][^\\][
Expand All @@ -43,7 +44,7 @@ index 5c6a593..e3b304e 100644
const group = textWithinFences.replace(/(?:(?![\n\r])\s)/g, ' ');
return `<pre data-code-raw="${withinFences}">${group}</pre>`;
},
@@ -176,7 +176,7 @@ export default class ExpensiMark {
@@ -177,7 +177,7 @@ export default class ExpensiMark {
{
name: 'reportMentions',

Expand All @@ -52,7 +53,7 @@ index 5c6a593..e3b304e 100644
replacement: '<mention-report>$1</mention-report>',
},

@@ -579,7 +579,7 @@ export default class ExpensiMark {
@@ -595,7 +595,7 @@ export default class ExpensiMark {
* @type {Number}
*/
this.currentQuoteDepth = 0;
Expand All @@ -61,16 +62,16 @@ index 5c6a593..e3b304e 100644

getHtmlRuleset(filterRules, disabledRules, shouldKeepRawInput) {
let rules = this.rules;
@@ -593,7 +593,7 @@ export default class ExpensiMark {
rules = _.filter(rules, (rule) => !_.contains(disabledRules, rule.name));
@@ -611,7 +611,7 @@ export default class ExpensiMark {
rules = _.filter(rules, hasDisabledRuleName);
}
return rules;
- }
+ },

/**
* Replaces markdown with html elements
@@ -640,7 +640,7 @@ export default class ExpensiMark {
@@ -659,7 +659,7 @@ export default class ExpensiMark {
}

return replacedText;
Expand All @@ -79,7 +80,7 @@ index 5c6a593..e3b304e 100644

/**
* Checks matched URLs for validity and replace valid links with html elements
@@ -749,7 +749,7 @@ export default class ExpensiMark {
@@ -768,7 +768,7 @@ export default class ExpensiMark {
}

return replacedText;
Expand All @@ -88,7 +89,7 @@ index 5c6a593..e3b304e 100644

/**
* Checks matched Emails for validity and replace valid links with html elements
@@ -788,7 +788,7 @@ export default class ExpensiMark {
@@ -807,7 +807,7 @@ export default class ExpensiMark {
replacedText = replacedText.concat(textToCheck.substr(startIndex));
}
return replacedText;
Expand All @@ -97,25 +98,25 @@ index 5c6a593..e3b304e 100644

/**
* replace block element with '\n' if :
@@ -830,7 +830,7 @@ export default class ExpensiMark {
});
@@ -852,7 +852,7 @@ export default class ExpensiMark {
splitText.forEach(processText);

return joinedText;
- }
+ },

/**
* Replaces HTML with markdown
@@ -861,7 +861,7 @@ export default class ExpensiMark {
generatedMarkdown = generatedMarkdown.replace(rule.regex, replacementFunction);
});
@@ -885,7 +885,7 @@ export default class ExpensiMark {

this.htmlToMarkdownRules.forEach(processRule);
return Str.htmlDecode(this.replaceBlockElementWithNewLine(generatedMarkdown));
- }
+ },

/**
* Convert HTML to text
@@ -885,7 +885,7 @@ export default class ExpensiMark {
@@ -909,7 +909,7 @@ export default class ExpensiMark {
// We use 'htmlDecode' instead of 'unescape' to replace entities like '&#32;'
replacedText = Str.htmlDecode(replacedText);
return replacedText;
Expand All @@ -124,7 +125,7 @@ index 5c6a593..e3b304e 100644

/**
* Modify text for Quotes replacing chevrons with html elements
@@ -948,7 +948,7 @@ export default class ExpensiMark {
@@ -972,7 +972,7 @@ export default class ExpensiMark {
replacedText = textToCheck;
}
return replacedText;
Expand All @@ -133,7 +134,7 @@ index 5c6a593..e3b304e 100644

/**
* Format the content of blockquote if the text matches the regex or else just return the original text
@@ -975,7 +975,7 @@ export default class ExpensiMark {
@@ -1000,7 +1000,7 @@ export default class ExpensiMark {
return replacement(textToFormat);
}
return textToCheck;
Expand All @@ -142,7 +143,7 @@ index 5c6a593..e3b304e 100644

/**
* Check if the input text includes only the open or the close tag of an element.
@@ -1014,7 +1014,7 @@ export default class ExpensiMark {
@@ -1039,7 +1039,7 @@ export default class ExpensiMark {

// If there are any tags left in the stack, they're unclosed
return tagStack.length !== 0;
Expand All @@ -151,7 +152,7 @@ index 5c6a593..e3b304e 100644

/**
* @param {String} comment
@@ -1036,7 +1036,7 @@ export default class ExpensiMark {
@@ -1061,7 +1061,7 @@ export default class ExpensiMark {
console.warn('Error parsing url in ExpensiMark.extractLinksInMarkdownComment', {error: e});
return undefined;
}
Expand All @@ -160,7 +161,7 @@ index 5c6a593..e3b304e 100644

/**
* Compares two markdown comments and returns a list of the links removed in a new comment.
@@ -1049,7 +1049,7 @@ export default class ExpensiMark {
@@ -1074,7 +1074,7 @@ export default class ExpensiMark {
const linksInOld = this.extractLinksInMarkdownComment(oldComment);
const linksInNew = this.extractLinksInMarkdownComment(newComment);
return linksInOld === undefined || linksInNew === undefined ? [] : _.difference(linksInOld, linksInNew);
Expand All @@ -169,7 +170,7 @@ index 5c6a593..e3b304e 100644

/**
* Escapes the content of an HTML attribute value
@@ -1068,3 +1068,5 @@ export default class ExpensiMark {
@@ -1093,3 +1093,5 @@ export default class ExpensiMark {
return _.escape(originalContent);
}
}
Expand Down
30 changes: 15 additions & 15 deletions parser/react-native-live-markdown-parser.js

Large diffs are not rendered by default.

49 changes: 30 additions & 19 deletions src/MarkdownTextInput.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ let focusTimeout: NodeJS.Timeout | null = null;
function normalizeValue(value: string) {
return value.replace(/\n$/, '');
}
// Adds one '\n' at the end of the string if it's missing
function denormalizeValue(value: string) {
return value.endsWith('\n') ? `${value}\n` : value;
}

// If an Input Method Editor is processing key input, the 'keyCode' is 229.
// https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode
Expand Down Expand Up @@ -175,7 +179,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
const dimensions = React.useRef<Dimensions | null>(null);

if (!history.current) {
history.current = new InputHistory(100);
history.current = new InputHistory(100, 150, value || '');
}

const flattenedStyle = useMemo(() => StyleSheet.flatten(style), [style]);
Expand Down Expand Up @@ -204,7 +208,8 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
}
const parsedText = ParseUtils.parseText(target, text, cursorPosition, customMarkdownStyles, !multiline);
if (history.current && shouldAddToHistory) {
history.current.debouncedAdd(parsedText.text, parsedText.cursorPosition);
// 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
history.current.throttledAdd(normalizeValue(parsedText.text), parsedText.cursorPosition);
}

return parsedText;
Expand Down Expand Up @@ -237,7 +242,8 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
(target: HTMLDivElement) => {
if (!history.current) return '';
const item = history.current.undo();
return parseText(target, item ? item.text : null, processedMarkdownStyle, item ? item.cursorPosition : null, false).text;
const undoValue = item ? denormalizeValue(item.text) : null;
return parseText(target, undoValue, processedMarkdownStyle, item ? item.cursorPosition : null, false).text;
},
[parseText, processedMarkdownStyle],
);
Expand All @@ -246,7 +252,8 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
(target: HTMLDivElement) => {
if (!history.current) return '';
const item = history.current.redo();
return parseText(target, item ? item.text : null, processedMarkdownStyle, item ? item.cursorPosition : null, false).text;
const redoValue = item ? denormalizeValue(item.text) : null;
return parseText(target, redoValue, processedMarkdownStyle, item ? item.cursorPosition : null, false).text;
},
[parseText, processedMarkdownStyle],
);
Expand Down Expand Up @@ -329,11 +336,18 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
if (!divRef.current || !(e.target instanceof HTMLElement)) {
return;
}

const nativeEvent = e.nativeEvent as MarkdownNativeEvent;
if (compositionRef.current && (nativeEvent.inputType !== 'insertCompositionText' || nativeEvent.data !== '*')) {
updateTextColor(divRef.current, e.target.innerText);
compositionRef.current = false;
return;

const changedText = e.target.innerText;

if (compositionRef.current) {
updateTextColor(divRef.current, changedText);

compositionRef.current = false;
return;
}

let text = '';
Expand All @@ -344,14 +358,22 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
case 'historyRedo':
text = redo(divRef.current);
break;
case 'insertFromPaste':
// 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
if (changedText.length > 2 && changedText[changedText.length - 2] !== '\n' && changedText[changedText.length - 1] === '\n') {
text = parseText(divRef.current, normalizeValue(changedText), processedMarkdownStyle).text;
break;
}
text = parseText(divRef.current, changedText, processedMarkdownStyle).text;
break;
default:
text = parseText(divRef.current, e.target.innerText, processedMarkdownStyle).text;
text = parseText(divRef.current, changedText, processedMarkdownStyle).text;
}
if (pasteRef?.current) {
pasteRef.current = false;
updateSelection(e);
}
updateTextColor(divRef.current, e.target.innerText);
updateTextColor(divRef.current, text);

if (onChange) {
const event = e as unknown as NativeSyntheticEvent<any>;
Expand Down Expand Up @@ -587,17 +609,6 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
CursorUtils.setCursorPosition(divRef.current, newSelection.start, newSelection.end);
}, [selection, updateRefSelectionVariables]);

useEffect(() => {
if (history.current?.history.length !== 0) {
return;
}
const currentValue = value ?? '';
history.current.add(currentValue, currentValue.length);

handleContentSizeChange();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
Expand Down
Loading

0 comments on commit 6dd6e8a

Please sign in to comment.