expensify-common 2.0.108 → 2.0.110

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/dist/CONST.d.ts CHANGED
@@ -804,6 +804,10 @@ declare const CONST: {
804
804
  readonly longFlightFare: "economy";
805
805
  readonly hotelStar: "fourStars";
806
806
  };
807
+ readonly PAYMENT_TYPE: {
808
+ readonly PAY_AT_HOTEL: "PAY_AT_HOTEL";
809
+ readonly PAY_AT_VENDOR: "PAY_AT_VENDOR";
810
+ };
807
811
  };
808
812
  readonly EXPENSIFY_DOMAINS: readonly ["expensify.com", "expensifail.com", "expensicorp.com"];
809
813
  readonly SUBSCRIPTION_CHANGE_REASONS: {
package/dist/CONST.js CHANGED
@@ -845,6 +845,10 @@ const CONST = {
845
845
  longFlightFare: 'economy',
846
846
  hotelStar: 'fourStars',
847
847
  },
848
+ PAYMENT_TYPE: {
849
+ PAY_AT_HOTEL: 'PAY_AT_HOTEL',
850
+ PAY_AT_VENDOR: 'PAY_AT_VENDOR',
851
+ },
848
852
  },
849
853
  // Expensify domains
850
854
  EXPENSIFY_DOMAINS: ['expensify.com', 'expensifail.com', 'expensicorp.com'],
@@ -131,6 +131,27 @@ export default class ExpensiMark {
131
131
  * 4. It's not the last element in the string.
132
132
  */
133
133
  replaceBlockElementWithNewLine(htmlString: string): string;
134
+ /**
135
+ * Unpacks nested quote HTML tags that have been packed by the 'quote' rule in this.rules for shouldKeepRawInput = false
136
+ *
137
+ * For example, it parses the following HTML:
138
+ * <blockquote>
139
+ * quote 1
140
+ * <blockquote>
141
+ * quote 2
142
+ * </blockquote>
143
+ * quote 3
144
+ * </blockquote>
145
+ *
146
+ * into:
147
+ * <blockquote> quote 1</blockquote>
148
+ * <blockquote><blockquote> quote 2</blockquote>
149
+ * <blockquote> quote 3</blockquote>
150
+ *
151
+ * Note that there will always be only a single closing tag, even if multiple opening tags exist.
152
+ * Only one closing tag is needed to detect if a nested quote has ended.
153
+ */
154
+ unpackNestedQuotes(text: string): string;
134
155
  /**
135
156
  * Replaces HTML with markdown
136
157
  */
@@ -140,13 +161,14 @@ export default class ExpensiMark {
140
161
  */
141
162
  htmlToText(htmlString: string, extras?: Extras): string;
142
163
  /**
143
- * Modify text for Quotes replacing chevrons with html elements
144
- */
145
- modifyTextForQuote(regex: RegExp, textToCheck: string, replacement: ReplacementFn): string;
146
- /**
147
- * Format the content of blockquote if the text matches the regex or else just return the original text
164
+ * Main text to html 'quote' parsing logic.
165
+ * Removes &gt;( ) from text and recursively calls replace function to process nested quotes and build blockquote HTML result.
166
+ * @param shouldKeepRawInput determines if the raw input should be kept for nested quotes.
148
167
  */
149
- formatTextForQuote(regex: RegExp, textToCheck: string, replacement: ReplacementFn): string;
168
+ replaceQuoteText(text: string, shouldKeepRawInput: boolean): {
169
+ replacedText: string;
170
+ shouldAddSpace: boolean;
171
+ };
150
172
  /**
151
173
  * Check if the input text includes only the open or the close tag of an element.
152
174
  */
@@ -306,54 +306,24 @@ class ExpensiMark {
306
306
  // block quotes naturally appear on their own line. Blockquotes should not appear in code fences or
307
307
  // inline code blocks. A single prepending space should be stripped if it exists
308
308
  process: (textToProcess, replacement, shouldKeepRawInput = false) => {
309
- const regex = /^(?:&gt;)+ +(?! )(?![^<]*(?:<\/pre>|<\/code>|<\/video>))([^\v\n\r]+)/gm;
309
+ const regex = /^(?:&gt;)+ +(?! )(?![^<]*(?:<\/pre>|<\/code>|<\/video>))([^\v\n\r]*)/gm;
310
+ let replacedText = this.replaceTextWithExtras(textToProcess, regex, EXTRAS_DEFAULT, replacement);
310
311
  if (shouldKeepRawInput) {
311
- const rawInputRegex = /^(?:&gt;)+ +(?! )(?![^<]*(?:<\/pre>|<\/code>|<\/video>))([^\v\n\r]*)/gm;
312
- return this.replaceTextWithExtras(textToProcess, rawInputRegex, EXTRAS_DEFAULT, replacement);
312
+ return replacedText;
313
313
  }
314
- return this.modifyTextForQuote(regex, textToProcess, replacement);
314
+ for (let i = this.maxQuoteDepth; i > 0; i--) {
315
+ replacedText = replacedText.replaceAll(`${'</blockquote>'.repeat(i)}\n${'<blockquote>'.repeat(i)}`, '\n');
316
+ }
317
+ replacedText = replacedText.replaceAll('</blockquote>\n', '</blockquote>');
318
+ return replacedText;
315
319
  },
316
320
  replacement: (_extras, g1) => {
317
- // We want to enable 2 options of nested heading inside the blockquote: "># heading" and "> # heading".
318
- // To do this we need to parse body of the quote without first space
319
- const handleMatch = (match) => match;
320
- const textToReplace = g1.replace(/^&gt;( )?/gm, handleMatch);
321
- const filterRules = ['heading1'];
322
- // if we don't reach the max quote depth we allow the recursive call to process possible quote
323
- if (this.currentQuoteDepth < this.maxQuoteDepth - 1) {
324
- filterRules.push('quote');
325
- this.currentQuoteDepth++;
326
- }
327
- const replacedText = this.replace(textToReplace, {
328
- filterRules,
329
- shouldEscapeText: false,
330
- shouldKeepRawInput: false,
331
- });
332
- this.currentQuoteDepth = 0;
333
- return `<blockquote>${replacedText}</blockquote>`;
321
+ const { replacedText } = this.replaceQuoteText(g1, false);
322
+ return `<blockquote>${replacedText || ' '}</blockquote>`;
334
323
  },
335
324
  rawInputReplacement: (_extras, g1) => {
336
- // We want to enable 2 options of nested heading inside the blockquote: "># heading" and "> # heading".
337
- // To do this we need to parse body of the quote without first space
338
- let isStartingWithSpace = false;
339
- const handleMatch = (_match, g2) => {
340
- isStartingWithSpace = !!g2;
341
- return '';
342
- };
343
- const textToReplace = g1.replace(/^&gt;( )?/gm, handleMatch);
344
- const filterRules = ['heading1'];
345
- // if we don't reach the max quote depth we allow the recursive call to process possible quote
346
- if (this.currentQuoteDepth < this.maxQuoteDepth - 1 || isStartingWithSpace) {
347
- filterRules.push('quote');
348
- this.currentQuoteDepth++;
349
- }
350
- const replacedText = this.replace(textToReplace, {
351
- filterRules,
352
- shouldEscapeText: false,
353
- shouldKeepRawInput: true,
354
- });
355
- this.currentQuoteDepth = 0;
356
- return `<blockquote>${isStartingWithSpace ? ' ' : ''}${replacedText}</blockquote>`;
325
+ const { replacedText, shouldAddSpace } = this.replaceQuoteText(g1, true);
326
+ return `<blockquote>${shouldAddSpace ? ' ' : ''}${replacedText}</blockquote>`;
357
327
  },
358
328
  },
359
329
  /**
@@ -957,8 +927,9 @@ class ExpensiMark {
957
927
  if (text.trim().length === 0 && !text.match(/\n/)) {
958
928
  return;
959
929
  }
930
+ const nextItem = splitText === null || splitText === void 0 ? void 0 : splitText[index + 1];
960
931
  // Insert '\n' unless it ends with '\n' or '>' or it's the last element, or if it's a header ('# ') with a space.
961
- if (text.match(/[\n|>][>]?[\s]?$/) || index === splitText.length - 1 || text === '# ') {
932
+ if ((nextItem && text.match(/>[\s]?$/) && !nextItem.startsWith('> ')) || text.match(/\n[\s]?$/) || index === splitText.length - 1 || text === '# ') {
962
933
  joinedText += text;
963
934
  }
964
935
  else {
@@ -968,6 +939,59 @@ class ExpensiMark {
968
939
  splitText.forEach(processText);
969
940
  return joinedText;
970
941
  }
942
+ /**
943
+ * Unpacks nested quote HTML tags that have been packed by the 'quote' rule in this.rules for shouldKeepRawInput = false
944
+ *
945
+ * For example, it parses the following HTML:
946
+ * <blockquote>
947
+ * quote 1
948
+ * <blockquote>
949
+ * quote 2
950
+ * </blockquote>
951
+ * quote 3
952
+ * </blockquote>
953
+ *
954
+ * into:
955
+ * <blockquote> quote 1</blockquote>
956
+ * <blockquote><blockquote> quote 2</blockquote>
957
+ * <blockquote> quote 3</blockquote>
958
+ *
959
+ * Note that there will always be only a single closing tag, even if multiple opening tags exist.
960
+ * Only one closing tag is needed to detect if a nested quote has ended.
961
+ */
962
+ unpackNestedQuotes(text) {
963
+ let parsedText = text.replace(/((<\/blockquote>)+(<br \/>)?)|(<br \/>)/g, (match) => {
964
+ return `${match}</split>`;
965
+ });
966
+ const splittedText = parsedText.split('</split>');
967
+ if (splittedText.length > 0 && splittedText[splittedText.length - 1] === '') {
968
+ splittedText.pop();
969
+ }
970
+ let count = 0;
971
+ parsedText = splittedText
972
+ .map((line) => {
973
+ const hasBR = line.endsWith('<br />');
974
+ if (line === '' && count === 0) {
975
+ return '';
976
+ }
977
+ const textLine = line.replace(/(<br \/>)$/g, '');
978
+ if (textLine.startsWith('<blockquote>')) {
979
+ count += (textLine.match(/<blockquote>/g) || []).length;
980
+ }
981
+ if (textLine.endsWith('</blockquote>')) {
982
+ count -= (textLine.match(/<\/blockquote>/g) || []).length;
983
+ if (count > 0) {
984
+ return `${textLine}${'<blockquote>'.repeat(count)}`;
985
+ }
986
+ }
987
+ if (count > 0) {
988
+ return `${textLine}${'</blockquote>'}${'<blockquote>'.repeat(count)}`;
989
+ }
990
+ return textLine + (hasBR ? '<br />' : '');
991
+ })
992
+ .join('');
993
+ return parsedText;
994
+ }
971
995
  /**
972
996
  * Replaces HTML with markdown
973
997
  */
@@ -979,6 +1003,7 @@ class ExpensiMark {
979
1003
  if (parseBodyTag) {
980
1004
  generatedMarkdown = parseBodyTag[2];
981
1005
  }
1006
+ generatedMarkdown = this.unpackNestedQuotes(generatedMarkdown);
982
1007
  const processRule = (rule) => {
983
1008
  // Pre-processes input HTML before applying regex
984
1009
  if (rule.pre) {
@@ -1004,85 +1029,30 @@ class ExpensiMark {
1004
1029
  return replacedText;
1005
1030
  }
1006
1031
  /**
1007
- * Modify text for Quotes replacing chevrons with html elements
1032
+ * Main text to html 'quote' parsing logic.
1033
+ * Removes &gt;( ) from text and recursively calls replace function to process nested quotes and build blockquote HTML result.
1034
+ * @param shouldKeepRawInput determines if the raw input should be kept for nested quotes.
1008
1035
  */
1009
- modifyTextForQuote(regex, textToCheck, replacement) {
1010
- let replacedText = '';
1011
- let textToFormat = '';
1012
- const match = textToCheck.match(regex);
1013
- // If there's matches we need to modify the quotes
1014
- if (match !== null) {
1015
- let insideCodefence = false;
1016
- // Split the textToCheck in lines
1017
- const textSplitted = textToCheck.split('\n');
1018
- for (let i = 0; i < textSplitted.length; i++) {
1019
- if (!insideCodefence) {
1020
- // We need to know when there is a start of codefence so we dont quote
1021
- insideCodefence = str_1.default.contains(textSplitted[i], '<pre>');
1022
- }
1023
- // Since the last space will be trimmed and would incorrectly disable a condition we check it manually
1024
- const isLastBlockquote = textSplitted[i] === '&gt;' && i === textSplitted.length - 1;
1025
- // We only want to modify lines starting with "&gt; " that is not codefence
1026
- if ((str_1.default.startsWith(textSplitted[i], '&gt; ') || isLastBlockquote) && !insideCodefence) {
1027
- if (textSplitted[i] === '&gt;') {
1028
- textToFormat += `${textSplitted[i]} \n`;
1029
- insideCodefence = true;
1030
- }
1031
- else {
1032
- textToFormat += `${textSplitted[i]}\n`;
1033
- }
1034
- }
1035
- else {
1036
- // Make sure we will only modify if we have Text needed to be formatted for quote
1037
- if (textToFormat !== '') {
1038
- replacedText += this.formatTextForQuote(regex, textToFormat, replacement);
1039
- textToFormat = '';
1040
- }
1041
- // We dont want a \n after the textSplitted if it is the last row
1042
- if (i === textSplitted.length - 1) {
1043
- replacedText += `${textSplitted[i]}`;
1044
- }
1045
- else {
1046
- replacedText += `${textSplitted[i]}\n`;
1047
- }
1048
- // We need to know when we are not inside codefence anymore
1049
- if (insideCodefence) {
1050
- insideCodefence = !str_1.default.contains(textSplitted[i], '</pre>');
1051
- }
1052
- }
1053
- }
1054
- // When loop ends we need the last quote to be formatted if we have quotes at last rows
1055
- if (textToFormat !== '') {
1056
- replacedText += this.formatTextForQuote(regex, textToFormat, replacement);
1057
- }
1058
- }
1059
- else {
1060
- // If we doesn't have matches make sure the function will return the same textToCheck
1061
- replacedText = textToCheck;
1062
- }
1063
- return replacedText;
1064
- }
1065
- /**
1066
- * Format the content of blockquote if the text matches the regex or else just return the original text
1067
- */
1068
- formatTextForQuote(regex, textToCheck, replacement) {
1069
- if (textToCheck.match(regex)) {
1070
- // Remove '&gt;' and trim the spaces between nested quotes
1071
- const formatRow = (row) => {
1072
- let quoteContent = row[4] === ' ' ? row.substr(5) : row.substr(4);
1073
- if (row === '&gt; ')
1074
- quoteContent = row.substr(4);
1075
- if (quoteContent.trimStart().startsWith('&gt;')) {
1076
- return quoteContent.trimStart();
1077
- }
1078
- return quoteContent;
1079
- };
1080
- let textToFormat = textToCheck.split('\n').map(formatRow).join('\n');
1081
- // Remove leading and trailing line breaks
1082
- textToFormat = textToFormat.replace(/^\n+|\n+$/g, '');
1083
- return replacement(EXTRAS_DEFAULT, textToFormat);
1036
+ replaceQuoteText(text, shouldKeepRawInput) {
1037
+ let isStartingWithSpace = false;
1038
+ const handleMatch = (_match, g2) => {
1039
+ isStartingWithSpace = !!g2;
1040
+ return '';
1041
+ };
1042
+ const textToReplace = text.replace(/^&gt;( )?/gm, handleMatch);
1043
+ const filterRules = ['heading1'];
1044
+ // If we don't reach the max quote depth, we allow the recursive call to process other possible quotes
1045
+ if (this.currentQuoteDepth < this.maxQuoteDepth - 1 && !isStartingWithSpace) {
1046
+ filterRules.push('quote');
1047
+ this.currentQuoteDepth++;
1084
1048
  }
1085
- return textToCheck;
1049
+ const replacedText = this.replace(textToReplace, {
1050
+ filterRules,
1051
+ shouldEscapeText: false,
1052
+ shouldKeepRawInput,
1053
+ });
1054
+ this.currentQuoteDepth = 0;
1055
+ return { replacedText, shouldAddSpace: isStartingWithSpace };
1086
1056
  }
1087
1057
  /**
1088
1058
  * Check if the input text includes only the open or the close tag of an element.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expensify-common",
3
- "version": "2.0.108",
3
+ "version": "2.0.110",
4
4
  "author": "Expensify, Inc.",
5
5
  "description": "Expensify libraries and components shared across different repos",
6
6
  "homepage": "https://expensify.com",