expensify-common 2.0.18 → 2.0.19

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.
@@ -1,155 +1,151 @@
1
- import type Logger from './Logger';
2
-
3
- declare type Replacement = (...args: string[], extras?: ExtrasObject) => string;
4
- declare type Name =
5
- | 'codeFence'
6
- | 'inlineCodeBlock'
7
- | 'email'
8
- | 'link'
9
- | 'hereMentions'
10
- | 'userMentions'
11
- | 'reportMentions'
12
- | 'autoEmail'
13
- | 'autolink'
14
- | 'quote'
15
- | 'italic'
16
- | 'bold'
17
- | 'strikethrough'
18
- | 'heading1'
19
- | 'newline'
20
- | 'replacepre'
21
- | 'listItem'
22
- | 'exclude'
23
- | 'anchor'
24
- | 'breakline'
25
- | 'blockquoteWrapHeadingOpen'
26
- | 'blockquoteWrapHeadingClose'
27
- | 'blockElementOpen'
28
- | 'blockElementClose'
29
- | 'stripTag';
30
- declare type Rule = {
31
- name: Name;
32
- process?: (textToProcess: string, replacement: Replacement) => string;
33
- regex?: RegExp;
34
- replacement: Replacement | string;
35
- pre?: (input: string) => string;
36
- post?: (input: string) => string;
37
- };
38
-
39
- declare type ExtrasObject = {
1
+ import Logger from './Logger';
2
+ type Extras = {
40
3
  reportIDToName?: Record<string, string>;
41
4
  accountIDToName?: Record<string, string>;
42
5
  cacheVideoAttributes?: (vidSource: string, attrs: string) => void;
43
- };
44
-
45
- declare type ExtraParamsForReplaceFunc = {
46
6
  videoAttributeCache?: Record<string, string>;
47
7
  };
48
-
8
+ type ReplacementFn = (extras: Extras, ...matches: string[]) => string;
9
+ type Replacement = ReplacementFn | string;
10
+ type ProcessFn = (textToProcess: string, replacement: Replacement, shouldKeepRawInput: boolean) => string;
11
+ type CommonRule = {
12
+ name: string;
13
+ replacement: Replacement;
14
+ rawInputReplacement?: Replacement;
15
+ pre?: (input: string) => string;
16
+ post?: (input: string) => string;
17
+ };
18
+ type RuleWithRegex = CommonRule & {
19
+ regex: RegExp;
20
+ };
21
+ type RuleWithProcess = CommonRule & {
22
+ process: ProcessFn;
23
+ };
24
+ type Rule = RuleWithRegex | RuleWithProcess;
25
+ type ReplaceOptions = {
26
+ extras?: Extras;
27
+ filterRules?: string[];
28
+ disabledRules?: string[];
29
+ shouldEscapeText?: boolean;
30
+ shouldKeepRawInput?: boolean;
31
+ };
49
32
  export default class ExpensiMark {
50
33
  static Log: Logger;
34
+ /**
35
+ * Set the logger to use for logging inside of the ExpensiMark class
36
+ * @param logger - The logger object to use
37
+ */
51
38
  static setLogger(logger: Logger): void;
52
-
39
+ /** Rules to apply to the text */
53
40
  rules: Rule[];
54
- htmlToMarkdownRules: Rule[];
55
- htmlToTextRules: Rule[];
41
+ /**
42
+ * The list of regex replacements to do on a HTML comment for converting it to markdown.
43
+ * Order of rules is important
44
+ */
45
+ htmlToMarkdownRules: RuleWithRegex[];
46
+ /**
47
+ * The list of rules to covert the HTML to text.
48
+ * Order of rules is important
49
+ */
50
+ htmlToTextRules: RuleWithRegex[];
51
+ /**
52
+ * The list of rules that we have to exclude in shouldKeepWhitespaceRules list.
53
+ */
54
+ whitespaceRulesToDisable: string[];
55
+ /**
56
+ * The list of rules that have to be applied when shouldKeepWhitespace flag is true.
57
+ */
58
+ filterRules: (rule: Rule) => boolean;
59
+ /**
60
+ * Filters rules to determine which should keep whitespace.
61
+ */
62
+ shouldKeepWhitespaceRules: Rule[];
63
+ /**
64
+ * maxQuoteDepth is the maximum depth of nested quotes that we want to support.
65
+ */
66
+ maxQuoteDepth: number;
67
+ /**
68
+ * currentQuoteDepth is the current depth of nested quotes that we are processing.
69
+ */
70
+ currentQuoteDepth: number;
56
71
  constructor();
72
+ /**
73
+ * Retrieves the HTML ruleset based on the provided filter rules, disabled rules, and shouldKeepRawInput flag.
74
+ * @param filterRules - An array of rule names to filter the ruleset.
75
+ * @param disabledRules - An array of rule names to disable in the ruleset.
76
+ * @param shouldKeepRawInput - A boolean flag indicating whether to keep raw input.
77
+ */
78
+ getHtmlRuleset(filterRules: string[], disabledRules: string[], shouldKeepRawInput: boolean): Rule[];
57
79
  /**
58
80
  * Replaces markdown with html elements
59
81
  *
60
82
  * @param text - Text to parse as markdown
61
- * @param options - Options to customize the markdown parser
62
- * @param options.filterRules=[] - An array of name of rules as defined in this class.
63
- * If not provided, all available rules will be applied. If provided, only the rules in the array will be applied.
64
- * @param options.disabledRules=[] - An array of name of rules as defined in this class.
83
+ * @param [options] - Options to customize the markdown parser
84
+ * @param [options.filterRules=[]] - An array of name of rules as defined in this class.
85
+ * If not provided, all available rules will be applied.
86
+ * @param [options.shouldEscapeText=true] - Whether or not the text should be escaped
87
+ * @param [options.disabledRules=[]] - An array of name of rules as defined in this class.
65
88
  * If not provided, all available rules will be applied. If provided, the rules in the array will be skipped.
66
- * @param options.shouldEscapeText=true - Whether or not the text should be escaped
67
- * @param options.shouldKeepRawInput=false - Whether or not the raw input should be kept and returned
68
- */
69
- replace(
70
- text: string,
71
- {
72
- filterRules,
73
- shouldEscapeText,
74
- shouldKeepRawInput,
75
- extras,
76
- }?: {
77
- filterRules?: Name[];
78
- disabledRules?: Name[];
79
- shouldEscapeText?: boolean;
80
- shouldKeepRawInput?: boolean;
81
- extras?: ExtraParamsForReplaceFunc;
82
- },
83
- ): string;
89
+ */
90
+ replace(text: string, { filterRules, shouldEscapeText, shouldKeepRawInput, disabledRules, extras }?: ReplaceOptions): string;
84
91
  /**
85
92
  * Checks matched URLs for validity and replace valid links with html elements
86
- *
87
- * @param regex
88
- * @param textToCheck
89
- * @param replacement
90
93
  */
91
- modifyTextForUrlLinks(regex: RegExp, textToCheck: string, replacement: Replacement): string;
94
+ modifyTextForUrlLinks(regex: RegExp, textToCheck: string, replacement: ReplacementFn): string;
92
95
  /**
93
96
  * Checks matched Emails for validity and replace valid links with html elements
94
- *
95
- * @param regex
96
- * @param textToCheck
97
- * @param replacement
98
97
  */
99
- modifyTextForEmailLinks(regex: RegExp, textToCheck: string, replacement: Replacement): string;
98
+ modifyTextForEmailLinks(regex: RegExp, textToCheck: string, replacement: ReplacementFn, shouldKeepRawInput: boolean): string;
100
99
  /**
101
100
  * replace block element with '\n' if :
102
101
  * 1. We have text within the element.
103
102
  * 2. The text does not end with a new line.
104
103
  * 3. The text does not have quote mark '>' .
105
104
  * 4. It's not the last element in the string.
106
- *
107
- * @param htmlString
108
105
  */
109
106
  replaceBlockElementWithNewLine(htmlString: string): string;
110
107
  /**
111
108
  * Replaces HTML with markdown
112
- *
113
- * @param htmlString
114
- * @param extras
115
109
  */
116
- htmlToMarkdown(htmlString: string, extras?: ExtrasObject): string;
110
+ htmlToMarkdown(htmlString: string, extras?: Extras): string;
117
111
  /**
118
112
  * Convert HTML to text
119
- *
120
- * @param htmlString
121
- * @param extras
122
113
  */
123
- htmlToText(htmlString: string, extras?: ExtrasObject): string;
114
+ htmlToText(htmlString: string, extras?: Extras): string;
124
115
  /**
125
116
  * Modify text for Quotes replacing chevrons with html elements
126
- *
127
- * @param regex
128
- * @param textToCheck
129
- * @param replacement
130
117
  */
131
- modifyTextForQuote(regex: RegExp, textToCheck: string, replacement: Replacement): string;
118
+ modifyTextForQuote(regex: RegExp, textToCheck: string, replacement: ReplacementFn): string;
132
119
  /**
133
120
  * Format the content of blockquote if the text matches the regex or else just return the original text
134
- *
135
- * @param regex
136
- * @param textToCheck
137
- * @param replacement
138
121
  */
139
- formatTextForQuote(regex: RegExp, textToCheck: string, replacement: Replacement): string;
122
+ formatTextForQuote(regex: RegExp, textToCheck: string, replacement: ReplacementFn): string;
140
123
  /**
141
124
  * Check if the input text includes only the open or the close tag of an element.
142
- *
143
- * @param textToCheck - Text to check
144
125
  */
145
126
  containsNonPairTag(textToCheck: string): boolean;
127
+ /**
128
+ * @returns array or undefined if exception occurs when executing regex matching
129
+ */
146
130
  extractLinksInMarkdownComment(comment: string): string[] | undefined;
147
131
  /**
148
132
  * Compares two markdown comments and returns a list of the links removed in a new comment.
149
- *
150
- * @param oldComment
151
- * @param newComment
152
133
  */
153
134
  getRemovedMarkdownLinks(oldComment: string, newComment: string): string[];
135
+ /**
136
+ * Escapes the content of an HTML attribute value
137
+ * @param content - string content that possible contains HTML
138
+ * @returns original MD content escaped for use in HTML attribute value
139
+ */
140
+ escapeAttributeContent(content: string): string;
141
+ /**
142
+ * Replaces text with a replacement based on a regex
143
+ * @param text - The text to replace
144
+ * @param regexp - The regex to match
145
+ * @param extras - The extras object
146
+ * @param replacement - The replacement string or function
147
+ * @returns The replaced text
148
+ */
149
+ replaceTextWithExtras(text: string, regexp: RegExp, extras: Extras, replacement: Replacement): string;
154
150
  }
155
151
  export {};
@@ -31,6 +31,7 @@ const Constants = __importStar(require("./CONST"));
31
31
  const UrlPatterns = __importStar(require("./Url"));
32
32
  const Logger_1 = __importDefault(require("./Logger"));
33
33
  const Utils = __importStar(require("./utils"));
34
+ const EXTRAS_DEFAULT = {};
34
35
  const MARKDOWN_LINK_REGEX = new RegExp(`\\[([^\\][]*(?:\\[[^\\][]*][^\\][]*)*)]\\(${UrlPatterns.MARKDOWN_URL_REGEX}\\)(?![^<]*(<\\/pre>|<\\/code>))`, 'gi');
35
36
  const MARKDOWN_IMAGE_REGEX = new RegExp(`\\!(?:\\[([^\\][]*(?:\\[[^\\][]*][^\\][]*)*)])?\\(${UrlPatterns.MARKDOWN_URL_REGEX}\\)(?![^<]*(<\\/pre>|<\\/code>))`, 'gi');
36
37
  const MARKDOWN_VIDEO_REGEX = new RegExp(`\\!(?:\\[([^\\][]*(?:\\[[^\\][]*][^\\][]*)*)])?\\(((${UrlPatterns.MARKDOWN_URL_REGEX})\\.(?:${Constants.CONST.VIDEO_EXTENSIONS.join('|')}))\\)(?![^<]*(<\\/pre>|<\\/code>))`, 'gi');
@@ -38,24 +39,26 @@ const SLACK_SPAN_NEW_LINE_TAG = '<span class="c-mrkdwn__br" data-stringify-type=
38
39
  class ExpensiMark {
39
40
  /**
40
41
  * Set the logger to use for logging inside of the ExpensiMark class
41
- * @param {Object} logger - The logger object to use
42
+ * @param logger - The logger object to use
42
43
  */
43
44
  static setLogger(logger) {
44
45
  ExpensiMark.Log = logger;
45
46
  }
46
47
  constructor() {
48
+ /**
49
+ * The list of rules that we have to exclude in shouldKeepWhitespaceRules list.
50
+ */
51
+ this.whitespaceRulesToDisable = ['newline', 'replacepre', 'replacebr', 'replaceh1br'];
47
52
  /**
48
53
  * The list of regex replacements to do on a comment. Check the link regex is first so links are processed
49
54
  * before other delimiters
50
- *
51
- * @type {Object[]}
52
55
  */
53
56
  this.rules = [
54
57
  // Apply the emoji first avoid applying any other formatting rules inside of it
55
58
  {
56
59
  name: 'emoji',
57
60
  regex: Constants.CONST.REG_EXP.EMOJI_RULE,
58
- replacement: (match) => `<emoji>${match}</emoji>`,
61
+ replacement: (_extras, match) => `<emoji>${match}</emoji>`,
59
62
  },
60
63
  /**
61
64
  * Apply the code-fence to avoid replacing anything inside of it that we're not supposed to
@@ -71,11 +74,11 @@ class ExpensiMark {
71
74
  // with the new lines here since they need to be converted into <br>. And we don't
72
75
  // want to do this anywhere else since that would break HTML.
73
76
  // &nbsp; will create styling issues so use &#32;
74
- replacement: (match, __, textWithinFences) => {
77
+ replacement: (_extras, _match, _g1, textWithinFences) => {
75
78
  const group = textWithinFences.replace(/(?:(?![\n\r])\s)/g, '&#32;');
76
79
  return `<pre>${group}</pre>`;
77
80
  },
78
- rawInputReplacement: (match, __, textWithinFences) => {
81
+ rawInputReplacement: (_extras, _match, _g1, textWithinFences) => {
79
82
  const group = textWithinFences.replace(/(?:(?![\n\r])\s)/g, '&#32;').replace(/<emoji>|<\/emoji>/g, '');
80
83
  return `<pre>${group}</pre>`;
81
84
  },
@@ -103,7 +106,7 @@ class ExpensiMark {
103
106
  const regex = new RegExp(`(?!\\[\\s*\\])\\[([^[\\]]*)]\\((mailto:)?${Constants.CONST.REG_EXP.MARKDOWN_EMAIL}\\)`, 'gim');
104
107
  return this.modifyTextForEmailLinks(regex, textToProcess, replacement, shouldKeepRawInput);
105
108
  },
106
- replacement: (match, g1, g2) => {
109
+ replacement: (_extras, match, g1, g2) => {
107
110
  if (g1.match(Constants.CONST.REG_EXP.EMOJIS) || !g1.trim()) {
108
111
  return match;
109
112
  }
@@ -112,7 +115,7 @@ class ExpensiMark {
112
115
  const formattedLabel = label === href ? g2 : label;
113
116
  return `<a href="${href}">${formattedLabel}</a>`;
114
117
  },
115
- rawInputReplacement: (match, g1, g2, g3) => {
118
+ rawInputReplacement: (_extras, match, g1, g2, g3) => {
116
119
  if (g1.match(Constants.CONST.REG_EXP.EMOJIS) || !g1.trim()) {
117
120
  return match;
118
121
  }
@@ -125,7 +128,7 @@ class ExpensiMark {
125
128
  name: 'heading1',
126
129
  process: (textToProcess, replacement, shouldKeepRawInput = false) => {
127
130
  const regexp = shouldKeepRawInput ? /^# ( *(?! )(?:(?!<pre>|\n|\r\n).)+)/gm : /^# +(?! )((?:(?!<pre>|\n|\r\n).)+)/gm;
128
- return textToProcess.replace(regexp, replacement);
131
+ return this.replaceTextWithExtras(textToProcess, regexp, EXTRAS_DEFAULT, replacement);
129
132
  },
130
133
  replacement: '<h1>$1</h1>',
131
134
  },
@@ -138,19 +141,16 @@ class ExpensiMark {
138
141
  name: 'video',
139
142
  regex: MARKDOWN_VIDEO_REGEX,
140
143
  /**
141
- * @param {string} match
142
- * @param {string} videoName - The first capture group - video name
143
- * @param {string} videoSource - The second capture group - video URL
144
- * @param {any[]} args - The rest capture groups and `extras` object. args[args.length-1] will the `extras` object
145
- * @return {string} Returns the HTML video tag
144
+ * @param extras - The extras object
145
+ * @param videoName - The first capture group - video name
146
+ * @param videoSource - The second capture group - video URL
147
+ * @return Returns the HTML video tag
146
148
  */
147
- replacement: (match, videoName, videoSource, ...args) => {
148
- const extras = args[args.length - 1];
149
+ replacement: (extras, _match, videoName, videoSource) => {
149
150
  const extraAttrs = extras && extras.videoAttributeCache && extras.videoAttributeCache[videoSource];
150
151
  return `<video data-expensify-source="${str_1.default.sanitizeURL(videoSource)}" ${extraAttrs || ''}>${videoName ? `${videoName}` : ''}</video>`;
151
152
  },
152
- rawInputReplacement: (match, videoName, videoSource, ...args) => {
153
- const extras = args[args.length - 1];
153
+ rawInputReplacement: (extras, _match, videoName, videoSource) => {
154
154
  const extraAttrs = extras && extras.videoAttributeCache && extras.videoAttributeCache[videoSource];
155
155
  return `<video data-expensify-source="${str_1.default.sanitizeURL(videoSource)}" data-raw-href="${videoSource}" data-link-variant="${typeof videoName === 'string' ? 'labeled' : 'auto'}" ${extraAttrs || ''}>${videoName ? `${videoName}` : ''}</video>`;
156
156
  },
@@ -165,8 +165,8 @@ class ExpensiMark {
165
165
  {
166
166
  name: 'image',
167
167
  regex: MARKDOWN_IMAGE_REGEX,
168
- replacement: (match, g1, g2) => `<img src="${str_1.default.sanitizeURL(g2)}"${g1 ? ` alt="${this.escapeAttributeContent(g1)}"` : ''} />`,
169
- rawInputReplacement: (match, g1, g2) => `<img src="${str_1.default.sanitizeURL(g2)}"${g1 ? ` alt="${this.escapeAttributeContent(g1)}"` : ''} data-raw-href="${g2}" data-link-variant="${typeof g1 === 'string' ? 'labeled' : 'auto'}" />`,
168
+ replacement: (_extras, _match, g1, g2) => `<img src="${str_1.default.sanitizeURL(g2)}"${g1 ? ` alt="${this.escapeAttributeContent(g1)}"` : ''} />`,
169
+ rawInputReplacement: (_extras, _match, g1, g2) => `<img src="${str_1.default.sanitizeURL(g2)}"${g1 ? ` alt="${this.escapeAttributeContent(g1)}"` : ''} data-raw-href="${g2}" data-link-variant="${typeof g1 === 'string' ? 'labeled' : 'auto'}" />`,
170
170
  },
171
171
  /**
172
172
  * Converts markdown style links to anchor tags e.g. [Expensify](https://www.expensify.com)
@@ -176,13 +176,13 @@ class ExpensiMark {
176
176
  {
177
177
  name: 'link',
178
178
  process: (textToProcess, replacement) => this.modifyTextForUrlLinks(MARKDOWN_LINK_REGEX, textToProcess, replacement),
179
- replacement: (match, g1, g2) => {
179
+ replacement: (_extras, match, g1, g2) => {
180
180
  if (g1.match(Constants.CONST.REG_EXP.EMOJIS) || !g1.trim()) {
181
181
  return match;
182
182
  }
183
183
  return `<a href="${str_1.default.sanitizeURL(g2)}" target="_blank" rel="noreferrer noopener">${g1.trim()}</a>`;
184
184
  },
185
- rawInputReplacement: (match, g1, g2) => {
185
+ rawInputReplacement: (_extras, match, g1, g2) => {
186
186
  if (g1.match(Constants.CONST.REG_EXP.EMOJIS) || !g1.trim()) {
187
187
  return match;
188
188
  }
@@ -199,7 +199,7 @@ class ExpensiMark {
199
199
  {
200
200
  name: 'hereMentions',
201
201
  regex: /([a-zA-Z0-9.!$%&+/=?^`{|}_-]?)(@here)([.!$%&+/=?^`{|}_-]?)(?=\b)(?!([\w'#%+-]*@(?:[a-z\d-]+\.)+[a-z]{2,}(?:\s|$|@here))|((?:(?!<a).)+)?<\/a>|[^<]*(<\/pre>|<\/code>))/gm,
202
- replacement: (match, g1, g2, g3) => {
202
+ replacement: (_extras, match, g1, g2, g3) => {
203
203
  if (!str_1.default.isValidMention(match)) {
204
204
  return match;
205
205
  }
@@ -229,7 +229,7 @@ class ExpensiMark {
229
229
  {
230
230
  name: 'userMentions',
231
231
  regex: new RegExp(`(@here|[a-zA-Z0-9.!$%&+=?^\`{|}-]?)(@${Constants.CONST.REG_EXP.EMAIL_PART}|@${Constants.CONST.REG_EXP.PHONE_PART})(?!((?:(?!<a).)+)?<\\/a>|[^<]*(<\\/pre>|<\\/code>))`, 'gim'),
232
- replacement: (match, g1, g2) => {
232
+ replacement: (_extras, match, g1, g2) => {
233
233
  const phoneNumberRegex = new RegExp(`^${Constants.CONST.REG_EXP.PHONE_PART}$`);
234
234
  const mention = g2.slice(1);
235
235
  const mentionWithoutSMSDomain = str_1.default.removeSMSDomain(mention);
@@ -239,7 +239,7 @@ class ExpensiMark {
239
239
  const phoneRegex = new RegExp(`^@${Constants.CONST.REG_EXP.PHONE_PART}$`);
240
240
  return `${g1}<mention-user>${g2}${phoneRegex.test(g2) ? `@${Constants.CONST.SMS.DOMAIN}` : ''}</mention-user>`;
241
241
  },
242
- rawInputReplacement: (match, g1, g2) => {
242
+ rawInputReplacement: (_extras, match, g1, g2) => {
243
243
  const phoneNumberRegex = new RegExp(`^${Constants.CONST.REG_EXP.PHONE_PART}$`);
244
244
  const mention = g2.slice(1);
245
245
  const mentionWithoutSMSDomain = str_1.default.removeSMSDomain(mention);
@@ -264,11 +264,11 @@ class ExpensiMark {
264
264
  const regex = new RegExp(`(?![^<]*>|[^<>]*<\\/(?!h1>))([_*~]*?)${UrlPatterns.MARKDOWN_URL_REGEX}\\1(?!((?:(?!<a).)+)?<\\/a>|[^<]*(<\\/pre>|<\\/code>|.+\\/>))`, 'gi');
265
265
  return this.modifyTextForUrlLinks(regex, textToProcess, replacement);
266
266
  },
267
- replacement: (match, g1, g2) => {
267
+ replacement: (_extras, _match, g1, g2) => {
268
268
  const href = str_1.default.sanitizeURL(g2);
269
269
  return `${g1}<a href="${href}" target="_blank" rel="noreferrer noopener">${g2}</a>${g1}`;
270
270
  },
271
- rawInputReplacement: (_match, g1, g2) => {
271
+ rawInputReplacement: (_extras, _match, g1, g2) => {
272
272
  const href = str_1.default.sanitizeURL(g2);
273
273
  return `${g1}<a href="${href}" data-raw-href="${g2}" data-link-variant="auto" target="_blank" rel="noreferrer noopener">${g2}</a>${g1}`;
274
274
  },
@@ -280,23 +280,38 @@ class ExpensiMark {
280
280
  // inline code blocks. A single prepending space should be stripped if it exists
281
281
  process: (textToProcess, replacement, shouldKeepRawInput = false) => {
282
282
  const regex = /^(?:&gt;)+ +(?! )(?![^<]*(?:<\/pre>|<\/code>))([^\v\n\r]+)/gm;
283
- const replaceFunction = (g1) => replacement(g1, shouldKeepRawInput);
284
283
  if (shouldKeepRawInput) {
285
284
  const rawInputRegex = /^(?:&gt;)+ +(?! )(?![^<]*(?:<\/pre>|<\/code>))([^\v\n\r]*)/gm;
286
- return textToProcess.replace(rawInputRegex, replaceFunction);
285
+ return this.replaceTextWithExtras(textToProcess, rawInputRegex, EXTRAS_DEFAULT, replacement);
287
286
  }
288
287
  return this.modifyTextForQuote(regex, textToProcess, replacement);
289
288
  },
290
- replacement: (g1, shouldKeepRawInput = false) => {
289
+ replacement: (_extras, g1) => {
290
+ // We want to enable 2 options of nested heading inside the blockquote: "># heading" and "> # heading".
291
+ // To do this we need to parse body of the quote without first space
292
+ const handleMatch = (match) => match;
293
+ const textToReplace = g1.replace(/^&gt;( )?/gm, handleMatch);
294
+ const filterRules = ['heading1'];
295
+ // if we don't reach the max quote depth we allow the recursive call to process possible quote
296
+ if (this.currentQuoteDepth < this.maxQuoteDepth - 1) {
297
+ filterRules.push('quote');
298
+ this.currentQuoteDepth++;
299
+ }
300
+ const replacedText = this.replace(textToReplace, {
301
+ filterRules,
302
+ shouldEscapeText: false,
303
+ shouldKeepRawInput: false,
304
+ });
305
+ this.currentQuoteDepth = 0;
306
+ return `<blockquote>${replacedText}</blockquote>`;
307
+ },
308
+ rawInputReplacement: (_extras, g1) => {
291
309
  // We want to enable 2 options of nested heading inside the blockquote: "># heading" and "> # heading".
292
310
  // To do this we need to parse body of the quote without first space
293
311
  let isStartingWithSpace = false;
294
- const handleMatch = (match, g2) => {
295
- if (shouldKeepRawInput) {
296
- isStartingWithSpace = !!g2;
297
- return '';
298
- }
299
- return match;
312
+ const handleMatch = (_match, g2) => {
313
+ isStartingWithSpace = !!g2;
314
+ return '';
300
315
  };
301
316
  const textToReplace = g1.replace(/^&gt;( )?/gm, handleMatch);
302
317
  const filterRules = ['heading1'];
@@ -308,7 +323,7 @@ class ExpensiMark {
308
323
  const replacedText = this.replace(textToReplace, {
309
324
  filterRules,
310
325
  shouldEscapeText: false,
311
- shouldKeepRawInput,
326
+ shouldKeepRawInput: true,
312
327
  });
313
328
  this.currentQuoteDepth = 0;
314
329
  return `<blockquote>${isStartingWithSpace ? ' ' : ''}${replacedText}</blockquote>`;
@@ -322,7 +337,7 @@ class ExpensiMark {
322
337
  {
323
338
  name: 'italic',
324
339
  regex: /(<(pre|code|a|mention-user)[^>]*>(.*?)<\/\2>)|((\b_+|\b)_((?![\s_])[\s\S]*?[^\s_](?<!\s))_(?![^\W_])(?![^<]*>)(?![^<]*(<\/pre>|<\/code>|<\/a>|<\/mention-user>)))/g,
325
- replacement: (match, html, tag, content, text, extraLeadingUnderscores, textWithinUnderscores) => {
340
+ replacement: (_extras, match, html, tag, content, text, extraLeadingUnderscores, textWithinUnderscores) => {
326
341
  // Skip any <pre>, <code>, <a>, <mention-user> tag contents
327
342
  if (html) {
328
343
  return html;
@@ -354,12 +369,12 @@ class ExpensiMark {
354
369
  // for * and ~: https://www.rexegg.com/regex-boundaries.html#notb
355
370
  name: 'bold',
356
371
  regex: /(?<!<[^>]*)\B\*(?![^<]*(?:<\/pre>|<\/code>|<\/a>))((?![\s*])[\s\S]*?[^\s*](?<!\s))\*\B(?![^<]*>)(?![^<]*(<\/pre>|<\/code>|<\/a>))/g,
357
- replacement: (match, g1) => (g1.includes('</pre>') || this.containsNonPairTag(g1) ? match : `<strong>${g1}</strong>`),
372
+ replacement: (_extras, match, g1) => (g1.includes('</pre>') || this.containsNonPairTag(g1) ? match : `<strong>${g1}</strong>`),
358
373
  },
359
374
  {
360
375
  name: 'strikethrough',
361
376
  regex: /(?<!<[^>]*)\B~((?![\s~])[\s\S]*?[^\s~](?<!\s))~\B(?![^<]*>)(?![^<]*(<\/pre>|<\/code>|<\/a>))/g,
362
- replacement: (match, g1) => (g1.includes('</pre>') || this.containsNonPairTag(g1) ? match : `<del>${g1}</del>`),
377
+ replacement: (_extras, match, g1) => (g1.includes('</pre>') || this.containsNonPairTag(g1) ? match : `<del>${g1}</del>`),
363
378
  },
364
379
  {
365
380
  name: 'newline',
@@ -382,7 +397,6 @@ class ExpensiMark {
382
397
  /**
383
398
  * The list of regex replacements to do on a HTML comment for converting it to markdown.
384
399
  * Order of rules is important
385
- * @type {Object[]}
386
400
  */
387
401
  this.htmlToMarkdownRules = [
388
402
  // Used to Exclude tags
@@ -445,7 +459,7 @@ class ExpensiMark {
445
459
  {
446
460
  name: 'quote',
447
461
  regex: /<(blockquote|q)(?:"[^"]*"|'[^']*'|[^'">])*>([\s\S]*?)<\/\1>(?![^<]*(<\/pre>|<\/code>))/gi,
448
- replacement: (match, g1, g2) => {
462
+ replacement: (_extras, _match, _g1, g2) => {
449
463
  // We remove the line break before heading inside quote to avoid adding extra line
450
464
  let resultString = g2
451
465
  .replace(/\n?(<h1># )/g, '$1')
@@ -480,12 +494,12 @@ class ExpensiMark {
480
494
  {
481
495
  name: 'codeFence',
482
496
  regex: /<(pre)(?:"[^"]*"|'[^']*'|[^'">])*>([\s\S]*?)(\n?)<\/\1>(?![^<]*(<\/pre>|<\/code>))/gi,
483
- replacement: (match, g1, g2) => `\`\`\`\n${g2}\n\`\`\``,
497
+ replacement: (_extras, _match, _g1, g2) => `\`\`\`\n${g2}\n\`\`\``,
484
498
  },
485
499
  {
486
500
  name: 'anchor',
487
501
  regex: /<(a)[^><]*href\s*=\s*(['"])(.*?)\2(?:".*?"|'.*?'|[^'"><])*>([\s\S]*?)<\/\1>(?![^<]*(<\/pre>|<\/code>))/gi,
488
- replacement: (match, g1, g2, g3, g4) => {
502
+ replacement: (_extras, _match, _g1, _g2, g3, g4) => {
489
503
  const email = g3.startsWith('mailto:') ? g3.slice(7) : '';
490
504
  if (email === g4) {
491
505
  return email;
@@ -496,7 +510,7 @@ class ExpensiMark {
496
510
  {
497
511
  name: 'image',
498
512
  regex: /<img[^><]*src\s*=\s*(['"])(.*?)\1(?:[^><]*alt\s*=\s*(['"])(.*?)\3)?[^><]*>*(?![^<][\s\S]*?(<\/pre>|<\/code>))/gi,
499
- replacement: (match, g1, g2, g3, g4) => {
513
+ replacement: (_extras, _match, _g1, g2, _g3, g4) => {
500
514
  if (g4) {
501
515
  return `![${g4}](${g2})`;
502
516
  }
@@ -507,16 +521,15 @@ class ExpensiMark {
507
521
  name: 'video',
508
522
  regex: /<video[^><]*data-expensify-source\s*=\s*(['"])(\S*?)\1(.*?)>([^><]*)<\/video>*(?![^<][\s\S]*?(<\/pre>|<\/code>))/gi,
509
523
  /**
510
- * @param {string} match The full match
511
- * @param {string} g1 {string} The first capture group
512
- * @param {string} videoSource - the second capture group - video source (video URL)
513
- * @param {string} videoAttrs - the third capture group - video attributes (data-expensify-width, data-expensify-height, etc...)
514
- * @param {string} videoName - the fourth capture group will be the video file name (the text between opening and closing video tags)
515
- * @param {any[]} args The rest of the arguments. args[args.length-1] will the `extras` object
516
- * @returns {string} Returns the markdown video tag
524
+ * @param extras - The extras object
525
+ * @param match The full match
526
+ * @param _g1 The first capture group
527
+ * @param videoSource - the second capture group - video source (video URL)
528
+ * @param videoAttrs - the third capture group - video attributes (data-expensify-width, data-expensify-height, etc...)
529
+ * @param videoName - the fourth capture group will be the video file name (the text between opening and closing video tags)
530
+ * @returns The markdown video tag
517
531
  */
518
- replacement: (match, g1, videoSource, videoAttrs, videoName, ...args) => {
519
- const extras = args[args.length - 1];
532
+ replacement: (extras, _match, _g1, videoSource, videoAttrs, videoName) => {
520
533
  if (videoAttrs && extras && extras.cacheVideoAttributes && typeof extras.cacheVideoAttributes === 'function') {
521
534
  extras.cacheVideoAttributes(videoSource, videoAttrs);
522
535
  }
@@ -529,7 +542,7 @@ class ExpensiMark {
529
542
  {
530
543
  name: 'reportMentions',
531
544
  regex: /<mention-report reportID="(\d+)" *\/>/gi,
532
- replacement: (match, g1, offset, string, extras) => {
545
+ replacement: (extras, _match, g1, _offset, _string) => {
533
546
  const reportToNameMap = extras.reportIDToName;
534
547
  if (!reportToNameMap || !reportToNameMap[g1]) {
535
548
  ExpensiMark.Log.alert('[ExpensiMark] Missing report name', { reportID: g1 });
@@ -541,14 +554,15 @@ class ExpensiMark {
541
554
  {
542
555
  name: 'userMention',
543
556
  regex: /(?:<mention-user accountID="(\d+)" *\/>)|(?:<mention-user>(.*?)<\/mention-user>)/gi,
544
- replacement: (match, g1, g2, offset, string, extras) => {
557
+ replacement: (extras, _match, g1, g2, _offset, _string) => {
558
+ var _a;
545
559
  if (g1) {
546
560
  const accountToNameMap = extras.accountIDToName;
547
561
  if (!accountToNameMap || !accountToNameMap[g1]) {
548
562
  ExpensiMark.Log.alert('[ExpensiMark] Missing account name', { accountID: g1 });
549
563
  return '@Hidden';
550
564
  }
551
- return `@${extras.accountIDToName[g1]}`;
565
+ return `@${(_a = extras.accountIDToName) === null || _a === void 0 ? void 0 : _a[g1]}`;
552
566
  }
553
567
  return str_1.default.removeSMSDomain(g2);
554
568
  },
@@ -557,7 +571,6 @@ class ExpensiMark {
557
571
  /**
558
572
  * The list of rules to covert the HTML to text.
559
573
  * Order of rules is important
560
- * @type {Object[]}
561
574
  */
562
575
  this.htmlToTextRules = [
563
576
  {
@@ -598,7 +611,7 @@ class ExpensiMark {
598
611
  {
599
612
  name: 'reportMentions',
600
613
  regex: /<mention-report reportID="(\d+)" *\/>/gi,
601
- replacement: (match, g1, offset, string, extras) => {
614
+ replacement: (extras, _match, g1, _offset, _string) => {
602
615
  const reportToNameMap = extras.reportIDToName;
603
616
  if (!reportToNameMap || !reportToNameMap[g1]) {
604
617
  ExpensiMark.Log.alert('[ExpensiMark] Missing report name', { reportID: g1 });
@@ -610,13 +623,14 @@ class ExpensiMark {
610
623
  {
611
624
  name: 'userMention',
612
625
  regex: /<mention-user accountID="(\d+)" *\/>/gi,
613
- replacement: (match, g1, offset, string, extras) => {
626
+ replacement: (extras, _match, g1, _offset, _string) => {
627
+ var _a;
614
628
  const accountToNameMap = extras.accountIDToName;
615
629
  if (!accountToNameMap || !accountToNameMap[g1]) {
616
630
  ExpensiMark.Log.alert('[ExpensiMark] Missing account name', { accountID: g1 });
617
631
  return '@Hidden';
618
632
  }
619
- return `@${extras.accountIDToName[g1]}`;
633
+ return `@${(_a = extras.accountIDToName) === null || _a === void 0 ? void 0 : _a[g1]}`;
620
634
  },
621
635
  },
622
636
  {
@@ -627,31 +641,34 @@ class ExpensiMark {
627
641
  ];
628
642
  /**
629
643
  * The list of rules that we have to exclude in shouldKeepWhitespaceRules list.
630
- * @type {Object[]}
631
644
  */
632
645
  this.whitespaceRulesToDisable = ['newline', 'replacepre', 'replacebr', 'replaceh1br'];
633
646
  /**
634
647
  * The list of rules that have to be applied when shouldKeepWhitespace flag is true.
635
- * @param {Object} rule - The rule to check.
636
- * @returns {boolean} Returns true if the rule should be applied, otherwise false.
648
+ * @param rule - The rule to check.
649
+ * @returns true if the rule should be applied, otherwise false.
637
650
  */
638
651
  this.filterRules = (rule) => !this.whitespaceRulesToDisable.includes(rule.name);
639
652
  /**
640
653
  * Filters rules to determine which should keep whitespace.
641
- * @returns {Object[]} The filtered rules.
654
+ * @returns The filtered rules.
642
655
  */
643
656
  this.shouldKeepWhitespaceRules = this.rules.filter(this.filterRules);
644
657
  /**
645
658
  * maxQuoteDepth is the maximum depth of nested quotes that we want to support.
646
- * @type {Number}
647
659
  */
648
660
  this.maxQuoteDepth = 3;
649
661
  /**
650
662
  * currentQuoteDepth is the current depth of nested quotes that we are processing.
651
- * @type {Number}
652
663
  */
653
664
  this.currentQuoteDepth = 0;
654
665
  }
666
+ /**
667
+ * Retrieves the HTML ruleset based on the provided filter rules, disabled rules, and shouldKeepRawInput flag.
668
+ * @param filterRules - An array of rule names to filter the ruleset.
669
+ * @param disabledRules - An array of rule names to disable in the ruleset.
670
+ * @param shouldKeepRawInput - A boolean flag indicating whether to keep raw input.
671
+ */
655
672
  getHtmlRuleset(filterRules, disabledRules, shouldKeepRawInput) {
656
673
  let rules = this.rules;
657
674
  const hasRuleName = (rule) => filterRules.includes(rule.name);
@@ -670,17 +687,15 @@ class ExpensiMark {
670
687
  /**
671
688
  * Replaces markdown with html elements
672
689
  *
673
- * @param {String} text - Text to parse as markdown
674
- * @param {Object} [options] - Options to customize the markdown parser
675
- * @param {String[]} [options.filterRules=[]] - An array of name of rules as defined in this class.
690
+ * @param text - Text to parse as markdown
691
+ * @param [options] - Options to customize the markdown parser
692
+ * @param [options.filterRules=[]] - An array of name of rules as defined in this class.
676
693
  * If not provided, all available rules will be applied.
677
- * @param {Boolean} [options.shouldEscapeText=true] - Whether or not the text should be escaped
678
- * @param {String[]} [options.disabledRules=[]] - An array of name of rules as defined in this class.
694
+ * @param [options.shouldEscapeText=true] - Whether or not the text should be escaped
695
+ * @param [options.disabledRules=[]] - An array of name of rules as defined in this class.
679
696
  * If not provided, all available rules will be applied. If provided, the rules in the array will be skipped.
680
- *
681
- * @returns {String}
682
697
  */
683
- replace(text, { filterRules = [], shouldEscapeText = true, shouldKeepRawInput = false, disabledRules = [], extras } = {}) {
698
+ replace(text, { filterRules = [], shouldEscapeText = true, shouldKeepRawInput = false, disabledRules = [], extras = EXTRAS_DEFAULT } = {}) {
684
699
  // This ensures that any html the user puts into the comment field shows as raw html
685
700
  let replacedText = shouldEscapeText ? Utils.escape(text) : text;
686
701
  const rules = this.getHtmlRuleset(filterRules, disabledRules, shouldKeepRawInput);
@@ -689,13 +704,12 @@ class ExpensiMark {
689
704
  if (rule.pre) {
690
705
  replacedText = rule.pre(replacedText);
691
706
  }
692
- const replacementFunction = shouldKeepRawInput && rule.rawInputReplacement ? rule.rawInputReplacement : rule.replacement;
693
- const replacementFnWithExtraParams = typeof replacementFunction === 'function' ? (...args) => replacementFunction(...args, extras) : replacementFunction;
694
- if (rule.process) {
695
- replacedText = rule.process(replacedText, replacementFnWithExtraParams, shouldKeepRawInput);
707
+ const replacement = shouldKeepRawInput && rule.rawInputReplacement ? rule.rawInputReplacement : rule.replacement;
708
+ if ('process' in rule) {
709
+ replacedText = rule.process(replacedText, replacement, shouldKeepRawInput);
696
710
  }
697
711
  else {
698
- replacedText = replacedText.replace(rule.regex, replacementFnWithExtraParams);
712
+ replacedText = this.replaceTextWithExtras(replacedText, rule.regex, extras, replacement);
699
713
  }
700
714
  // Post-process text after applying regex
701
715
  if (rule.post) {
@@ -706,8 +720,7 @@ class ExpensiMark {
706
720
  rules.forEach(processRule);
707
721
  }
708
722
  catch (e) {
709
- // eslint-disable-next-line no-console
710
- console.warn('Error replacing text with html in ExpensiMark.replace', { error: e });
723
+ ExpensiMark.Log.alert('Error replacing text with html in ExpensiMark.replace', { error: e });
711
724
  // We want to return text without applying rules if exception occurs during replacing
712
725
  return shouldEscapeText ? Utils.escape(text) : text;
713
726
  }
@@ -715,12 +728,6 @@ class ExpensiMark {
715
728
  }
716
729
  /**
717
730
  * Checks matched URLs for validity and replace valid links with html elements
718
- *
719
- * @param {RegExp} regex
720
- * @param {String} textToCheck
721
- * @param {Function} replacement
722
- *
723
- * @returns {String}
724
731
  */
725
732
  modifyTextForUrlLinks(regex, textToCheck, replacement) {
726
733
  let match = regex.exec(textToCheck);
@@ -803,7 +810,7 @@ class ExpensiMark {
803
810
  filterRules: ['bold', 'strikethrough', 'italic'],
804
811
  shouldEscapeText: false,
805
812
  });
806
- replacedText = replacedText.concat(replacement(match[0], linkText, url));
813
+ replacedText = replacedText.concat(replacement(EXTRAS_DEFAULT, match[0], linkText, url));
807
814
  }
808
815
  startIndex = match.index + match[0].length;
809
816
  // Now we move to the next match that the js regex found in the text
@@ -816,13 +823,6 @@ class ExpensiMark {
816
823
  }
817
824
  /**
818
825
  * Checks matched Emails for validity and replace valid links with html elements
819
- *
820
- * @param {RegExp} regex
821
- * @param {String} textToCheck
822
- * @param {Function} replacement
823
- * @param {Boolean} shouldKeepRawInput
824
- *
825
- * @returns {String}
826
826
  */
827
827
  modifyTextForEmailLinks(regex, textToCheck, replacement, shouldKeepRawInput) {
828
828
  let match = regex.exec(textToCheck);
@@ -836,8 +836,8 @@ class ExpensiMark {
836
836
  filterRules: ['bold', 'strikethrough', 'italic'],
837
837
  shouldEscapeText: false,
838
838
  });
839
- // rawInputReplacment needs to be called with additional parameters from match
840
- const replacedMatch = shouldKeepRawInput ? replacement(match[0], linkText, match[2], match[3]) : replacement(match[0], linkText, match[3]);
839
+ // rawInputReplacement needs to be called with additional parameters from match
840
+ const replacedMatch = shouldKeepRawInput ? replacement(EXTRAS_DEFAULT, match[0], linkText, match[2], match[3]) : replacement(EXTRAS_DEFAULT, match[0], linkText, match[3]);
841
841
  replacedText = replacedText.concat(replacedMatch);
842
842
  startIndex = match.index + match[0].length;
843
843
  // Now we move to the next match that the js regex found in the text
@@ -854,9 +854,6 @@ class ExpensiMark {
854
854
  * 2. The text does not end with a new line.
855
855
  * 3. The text does not have quote mark '>' .
856
856
  * 4. It's not the last element in the string.
857
- *
858
- * @param {String} htmlString
859
- * @returns {String}
860
857
  */
861
858
  replaceBlockElementWithNewLine(htmlString) {
862
859
  // eslint-disable-next-line max-len
@@ -888,13 +885,8 @@ class ExpensiMark {
888
885
  }
889
886
  /**
890
887
  * Replaces HTML with markdown
891
- *
892
- * @param {String} htmlString
893
- * @param {Object} extras
894
- *
895
- * @returns {String}
896
888
  */
897
- htmlToMarkdown(htmlString, extras = {}) {
889
+ htmlToMarkdown(htmlString, extras = EXTRAS_DEFAULT) {
898
890
  let generatedMarkdown = htmlString;
899
891
  const body = /<(body)(?:"[^"]*"|'[^']*'|[^'"><])*>(?:\n|\r\n)?([\s\S]*?)(?:\n|\r\n)?<\/\1>(?![^<]*(<\/pre>|<\/code>))/im;
900
892
  const parseBodyTag = generatedMarkdown.match(body);
@@ -907,27 +899,18 @@ class ExpensiMark {
907
899
  if (rule.pre) {
908
900
  generatedMarkdown = rule.pre(generatedMarkdown);
909
901
  }
910
- // if replacement is a function, we want to pass optional extras to it
911
- const replacementFunction = Utils.isFunction(rule.replacement) ? (...args) => rule.replacement(...args, extras) : rule.replacement;
912
- generatedMarkdown = generatedMarkdown.replace(rule.regex, replacementFunction);
902
+ generatedMarkdown = this.replaceTextWithExtras(generatedMarkdown, rule.regex, extras, rule.replacement);
913
903
  };
914
904
  this.htmlToMarkdownRules.forEach(processRule);
915
905
  return str_1.default.htmlDecode(this.replaceBlockElementWithNewLine(generatedMarkdown));
916
906
  }
917
907
  /**
918
908
  * Convert HTML to text
919
- *
920
- * @param {String} htmlString
921
- * @param {Object} extras
922
- *
923
- * @returns {String}
924
909
  */
925
- htmlToText(htmlString, extras = {}) {
910
+ htmlToText(htmlString, extras = EXTRAS_DEFAULT) {
926
911
  let replacedText = htmlString;
927
912
  const processRule = (rule) => {
928
- // if replacement is a function, we want to pass optional extras to it
929
- const replacementFunction = Utils.isFunction(rule.replacement) ? (...args) => rule.replacement(...args, extras) : rule.replacement;
930
- replacedText = replacedText.replace(rule.regex, replacementFunction);
913
+ replacedText = this.replaceTextWithExtras(replacedText, rule.regex, extras, rule.replacement);
931
914
  };
932
915
  this.htmlToTextRules.forEach(processRule);
933
916
  // Unescaping because the text is escaped in 'replace' function
@@ -937,12 +920,6 @@ class ExpensiMark {
937
920
  }
938
921
  /**
939
922
  * Modify text for Quotes replacing chevrons with html elements
940
- *
941
- * @param {RegExp} regex
942
- * @param {String} textToCheck
943
- * @param {Function} replacement
944
- *
945
- * @returns {String}
946
923
  */
947
924
  modifyTextForQuote(regex, textToCheck, replacement) {
948
925
  let replacedText = '';
@@ -994,12 +971,6 @@ class ExpensiMark {
994
971
  }
995
972
  /**
996
973
  * Format the content of blockquote if the text matches the regex or else just return the original text
997
- *
998
- * @param {RegExp} regex
999
- * @param {String} textToCheck
1000
- * @param {Function} replacement
1001
- *
1002
- * @returns {String}
1003
974
  */
1004
975
  formatTextForQuote(regex, textToCheck, replacement) {
1005
976
  if (textToCheck.match(regex)) {
@@ -1014,16 +985,12 @@ class ExpensiMark {
1014
985
  let textToFormat = textToCheck.split('\n').map(formatRow).join('\n');
1015
986
  // Remove leading and trailing line breaks
1016
987
  textToFormat = textToFormat.replace(/^\n+|\n+$/g, '');
1017
- return replacement(textToFormat);
988
+ return replacement(EXTRAS_DEFAULT, textToFormat);
1018
989
  }
1019
990
  return textToCheck;
1020
991
  }
1021
992
  /**
1022
993
  * Check if the input text includes only the open or the close tag of an element.
1023
- *
1024
- * @param {String} textToCheck - Text to check
1025
- *
1026
- * @returns {Boolean}
1027
994
  */
1028
995
  containsNonPairTag(textToCheck) {
1029
996
  // Create a regular expression to match HTML tags
@@ -1053,8 +1020,7 @@ class ExpensiMark {
1053
1020
  return tagStack.length !== 0;
1054
1021
  }
1055
1022
  /**
1056
- * @param {String} comment
1057
- * @returns {Array} or undefined if exception occurs when executing regex matching
1023
+ * @returns array or undefined if exception occurs when executing regex matching
1058
1024
  */
1059
1025
  extractLinksInMarkdownComment(comment) {
1060
1026
  try {
@@ -1068,17 +1034,12 @@ class ExpensiMark {
1068
1034
  return links;
1069
1035
  }
1070
1036
  catch (e) {
1071
- // eslint-disable-next-line no-console
1072
- console.warn('Error parsing url in ExpensiMark.extractLinksInMarkdownComment', { error: e });
1037
+ ExpensiMark.Log.alert('Error parsing url in ExpensiMark.extractLinksInMarkdownComment', { error: e });
1073
1038
  return undefined;
1074
1039
  }
1075
1040
  }
1076
1041
  /**
1077
1042
  * Compares two markdown comments and returns a list of the links removed in a new comment.
1078
- *
1079
- * @param {String} oldComment
1080
- * @param {String} newComment
1081
- * @returns {Array}
1082
1043
  */
1083
1044
  getRemovedMarkdownLinks(oldComment, newComment) {
1084
1045
  const linksInOld = this.extractLinksInMarkdownComment(oldComment);
@@ -1087,8 +1048,8 @@ class ExpensiMark {
1087
1048
  }
1088
1049
  /**
1089
1050
  * Escapes the content of an HTML attribute value
1090
- * @param {String} content - string content that possible contains HTML
1091
- * @returns {String} - original MD content escaped for use in HTML attribute value
1051
+ * @param content - string content that possible contains HTML
1052
+ * @returns original MD content escaped for use in HTML attribute value
1092
1053
  */
1093
1054
  escapeAttributeContent(content) {
1094
1055
  let originalContent = this.htmlToMarkdown(content);
@@ -1100,9 +1061,24 @@ class ExpensiMark {
1100
1061
  originalContent = str_1.default.replaceAll(originalContent, '\n', '');
1101
1062
  return Utils.escape(originalContent);
1102
1063
  }
1064
+ /**
1065
+ * Replaces text with a replacement based on a regex
1066
+ * @param text - The text to replace
1067
+ * @param regexp - The regex to match
1068
+ * @param extras - The extras object
1069
+ * @param replacement - The replacement string or function
1070
+ * @returns The replaced text
1071
+ */
1072
+ replaceTextWithExtras(text, regexp, extras, replacement) {
1073
+ if (typeof replacement === 'function') {
1074
+ // if the replacement is a function, we pass the extras object to it
1075
+ return text.replace(regexp, (...args) => replacement(extras, ...args));
1076
+ }
1077
+ return text.replace(regexp, replacement);
1078
+ }
1103
1079
  }
1104
1080
  ExpensiMark.Log = new Logger_1.default({
1105
- serverLoggingCallback: () => { },
1081
+ serverLoggingCallback: () => undefined,
1106
1082
  // eslint-disable-next-line no-console
1107
1083
  clientLoggingCallback: (message) => console.warn(message),
1108
1084
  isDebug: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expensify-common",
3
- "version": "2.0.18",
3
+ "version": "2.0.19",
4
4
  "author": "Expensify, Inc.",
5
5
  "description": "Expensify libraries and components shared across different repos",
6
6
  "homepage": "https://expensify.com",