expensify-common 2.0.43 → 2.0.44
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/ExpensiMark.d.ts +27 -0
- package/dist/ExpensiMark.js +166 -0
- package/package.json +1 -1
package/dist/ExpensiMark.d.ts
CHANGED
|
@@ -29,6 +29,12 @@ type ReplaceOptions = {
|
|
|
29
29
|
shouldEscapeText?: boolean;
|
|
30
30
|
shouldKeepRawInput?: boolean;
|
|
31
31
|
};
|
|
32
|
+
type TruncateOptions = {
|
|
33
|
+
ellipsis?: string;
|
|
34
|
+
truncateLastWord?: boolean;
|
|
35
|
+
slop?: number;
|
|
36
|
+
removeImageTag?: boolean;
|
|
37
|
+
};
|
|
32
38
|
export default class ExpensiMark {
|
|
33
39
|
static Log: Logger;
|
|
34
40
|
/**
|
|
@@ -138,6 +144,27 @@ export default class ExpensiMark {
|
|
|
138
144
|
* @returns original MD content escaped for use in HTML attribute value
|
|
139
145
|
*/
|
|
140
146
|
escapeAttributeContent(content: string): string;
|
|
147
|
+
/**
|
|
148
|
+
* Determines the end position to truncate the HTML content while considering word boundaries.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} content - The HTML content to be truncated.
|
|
151
|
+
* @param {number} tailPosition - The position up to which the content should be considered.
|
|
152
|
+
* @param {number} maxLength - The maximum length of the truncated content.
|
|
153
|
+
* @param {number} totalLength - The length of the content processed so far.
|
|
154
|
+
* @param {Object} opts - Options to customize the truncation.
|
|
155
|
+
* @returns {number} The calculated position to truncate the content.
|
|
156
|
+
*/
|
|
157
|
+
getEndPosition(content: string, tailPosition: number | undefined, maxLength: number, totalLength: number, opts: TruncateOptions): number;
|
|
158
|
+
/**
|
|
159
|
+
* Truncate HTML string and keep tag safe.
|
|
160
|
+
* pulled from https://github.com/huang47/nodejs-html-truncate/blob/master/lib/truncate.js
|
|
161
|
+
*
|
|
162
|
+
* @param {string} html - The string that needs to be truncated
|
|
163
|
+
* @param {number} maxLength - Length of truncated string
|
|
164
|
+
* @param {Object} [options] - Optional configuration options
|
|
165
|
+
* @returns {string} The truncated string
|
|
166
|
+
*/
|
|
167
|
+
truncateHTML(html: string, maxLength: number, options?: TruncateOptions): string;
|
|
141
168
|
/**
|
|
142
169
|
* Replaces text with a replacement based on a regex
|
|
143
170
|
* @param text - The text to replace
|
package/dist/ExpensiMark.js
CHANGED
|
@@ -1093,6 +1093,172 @@ class ExpensiMark {
|
|
|
1093
1093
|
originalContent = str_1.default.replaceAll(originalContent, '\n', '');
|
|
1094
1094
|
return Utils.escape(originalContent);
|
|
1095
1095
|
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Determines the end position to truncate the HTML content while considering word boundaries.
|
|
1098
|
+
*
|
|
1099
|
+
* @param {string} content - The HTML content to be truncated.
|
|
1100
|
+
* @param {number} tailPosition - The position up to which the content should be considered.
|
|
1101
|
+
* @param {number} maxLength - The maximum length of the truncated content.
|
|
1102
|
+
* @param {number} totalLength - The length of the content processed so far.
|
|
1103
|
+
* @param {Object} opts - Options to customize the truncation.
|
|
1104
|
+
* @returns {number} The calculated position to truncate the content.
|
|
1105
|
+
*/
|
|
1106
|
+
getEndPosition(content, tailPosition, maxLength, totalLength, opts) {
|
|
1107
|
+
const WORD_BREAK_REGEX = /\W+/g;
|
|
1108
|
+
// Calculate the default position to truncate based on the maximum length and the length of the content processed so far
|
|
1109
|
+
const defaultPosition = maxLength - totalLength;
|
|
1110
|
+
// Define the slop value, which determines the tolerance for cutting off content near the maximum length
|
|
1111
|
+
const slop = opts.slop;
|
|
1112
|
+
if (!slop)
|
|
1113
|
+
return defaultPosition;
|
|
1114
|
+
// Initialize the position to the default position
|
|
1115
|
+
let position = defaultPosition;
|
|
1116
|
+
// Determine if the default position is considered "short" based on the slop value
|
|
1117
|
+
const isShort = defaultPosition < slop;
|
|
1118
|
+
// Calculate the position within the slop range
|
|
1119
|
+
const slopPos = isShort ? defaultPosition : slop - 1;
|
|
1120
|
+
// Extract the substring to analyze for word boundaries, considering the slop and tail position
|
|
1121
|
+
const substr = content.slice(isShort ? 0 : defaultPosition - slop, tailPosition !== undefined ? tailPosition : defaultPosition + slop);
|
|
1122
|
+
// Find the first word boundary within the substring
|
|
1123
|
+
const wordBreakMatch = WORD_BREAK_REGEX.exec(substr);
|
|
1124
|
+
// Adjust the position to avoid truncating in the middle of a word if the option is enabled
|
|
1125
|
+
if (!opts.truncateLastWord) {
|
|
1126
|
+
if (tailPosition && substr.length <= tailPosition) {
|
|
1127
|
+
// If tail position is defined and the substring length is within the tail position, set position to the substring length
|
|
1128
|
+
position = substr.length;
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
// Iterate through word boundary matches to adjust the position
|
|
1132
|
+
while (wordBreakMatch !== null) {
|
|
1133
|
+
if (wordBreakMatch.index < slopPos) {
|
|
1134
|
+
// If the word boundary is before the slop position, adjust position backward
|
|
1135
|
+
position = defaultPosition - (slopPos - wordBreakMatch.index);
|
|
1136
|
+
if (wordBreakMatch.index === 0 && defaultPosition <= 1) {
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
else if (wordBreakMatch.index === slopPos) {
|
|
1141
|
+
// If the word boundary is at the slop position, set position to the default position
|
|
1142
|
+
position = defaultPosition;
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
else {
|
|
1146
|
+
// If the word boundary is after the slop position, adjust position forward
|
|
1147
|
+
position = defaultPosition + (wordBreakMatch.index - slopPos);
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
// If the character at the determined position is a whitespace, adjust position backward
|
|
1153
|
+
if (content.charAt(position - 1).match(/\s$/)) {
|
|
1154
|
+
position--;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
// Return the calculated position to truncate the content
|
|
1158
|
+
return position;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Truncate HTML string and keep tag safe.
|
|
1162
|
+
* pulled from https://github.com/huang47/nodejs-html-truncate/blob/master/lib/truncate.js
|
|
1163
|
+
*
|
|
1164
|
+
* @param {string} html - The string that needs to be truncated
|
|
1165
|
+
* @param {number} maxLength - Length of truncated string
|
|
1166
|
+
* @param {Object} [options] - Optional configuration options
|
|
1167
|
+
* @returns {string} The truncated string
|
|
1168
|
+
*/
|
|
1169
|
+
truncateHTML(html, maxLength, options) {
|
|
1170
|
+
const EMPTY_STRING = '';
|
|
1171
|
+
const DEFAULT_TRUNCATE_SYMBOL = '...';
|
|
1172
|
+
const DEFAULT_SLOP = Math.min(10, maxLength);
|
|
1173
|
+
const tagsStack = [];
|
|
1174
|
+
const KEY_VALUE_REGEX = '((?:\\s+(?:\\w+|-)+(?:\\s*=\\s*(?:"(?:\\\\.|[^"\\\\])*"|\'(?:\\\\.|[^\'\\\\])*\'|[^\'">\\s]+))?)*)';
|
|
1175
|
+
const IS_CLOSE_REGEX = '\\s*\\/?\\s*';
|
|
1176
|
+
const CLOSE_REGEX = '\\s*\\/\\s*';
|
|
1177
|
+
const SELF_CLOSE_REGEX = new RegExp(`<\\/?(\\w+)${KEY_VALUE_REGEX}${CLOSE_REGEX}>`);
|
|
1178
|
+
const HTML_TAG_REGEX = new RegExp(`<\\/?(\\w+)${KEY_VALUE_REGEX}${IS_CLOSE_REGEX}>`);
|
|
1179
|
+
const URL_REGEX = /(((ftp|https?):\/\/)[\\-\w@:%_\\+.~#?,&\\/\\/=]+)|((mailto:)?[_.\w\\-]+@([\w][\w\\-]+\.)+[a-zA-Z]{2,3})/g;
|
|
1180
|
+
const IMAGE_TAG_REGEX = new RegExp(`<img\\s*${KEY_VALUE_REGEX}${CLOSE_REGEX}>`);
|
|
1181
|
+
let truncatedContent = EMPTY_STRING;
|
|
1182
|
+
let totalLength = 0;
|
|
1183
|
+
let matches = HTML_TAG_REGEX.exec(html);
|
|
1184
|
+
let endResult;
|
|
1185
|
+
let index;
|
|
1186
|
+
let tag;
|
|
1187
|
+
let selfClose = null;
|
|
1188
|
+
let htmlString = html;
|
|
1189
|
+
const opts = Object.assign({ ellipsis: DEFAULT_TRUNCATE_SYMBOL, truncateLastWord: true, slop: DEFAULT_SLOP }, options);
|
|
1190
|
+
function removeImageTag(content) {
|
|
1191
|
+
const match = IMAGE_TAG_REGEX.exec(content);
|
|
1192
|
+
if (!match) {
|
|
1193
|
+
return content;
|
|
1194
|
+
}
|
|
1195
|
+
const matchIndex = match.index;
|
|
1196
|
+
const matchLength = match[0].length;
|
|
1197
|
+
return content.substring(0, matchIndex) + content.substring(matchIndex + matchLength);
|
|
1198
|
+
}
|
|
1199
|
+
function closeTags(tags) {
|
|
1200
|
+
return tags
|
|
1201
|
+
.reverse()
|
|
1202
|
+
.map((mappedTag) => {
|
|
1203
|
+
return `</${mappedTag}>`;
|
|
1204
|
+
})
|
|
1205
|
+
.join('');
|
|
1206
|
+
}
|
|
1207
|
+
while (matches) {
|
|
1208
|
+
matches = HTML_TAG_REGEX.exec(htmlString);
|
|
1209
|
+
if (!matches) {
|
|
1210
|
+
if (totalLength >= maxLength) {
|
|
1211
|
+
break;
|
|
1212
|
+
}
|
|
1213
|
+
matches = URL_REGEX.exec(htmlString);
|
|
1214
|
+
if (!matches || matches.index >= maxLength) {
|
|
1215
|
+
truncatedContent += htmlString.substring(0, this.getEndPosition(htmlString, undefined, maxLength, totalLength, opts));
|
|
1216
|
+
break;
|
|
1217
|
+
}
|
|
1218
|
+
while (matches) {
|
|
1219
|
+
endResult = matches[0];
|
|
1220
|
+
if (endResult !== null) {
|
|
1221
|
+
index = matches.index;
|
|
1222
|
+
truncatedContent += htmlString.substring(0, index + endResult.length - totalLength);
|
|
1223
|
+
htmlString = htmlString.substring(index + endResult.length);
|
|
1224
|
+
matches = URL_REGEX.exec(htmlString);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
endResult = matches[0];
|
|
1230
|
+
index = matches.index;
|
|
1231
|
+
if (totalLength + index > maxLength) {
|
|
1232
|
+
truncatedContent += htmlString.substring(0, this.getEndPosition(htmlString, index, maxLength, totalLength, opts));
|
|
1233
|
+
break;
|
|
1234
|
+
}
|
|
1235
|
+
else {
|
|
1236
|
+
totalLength += index;
|
|
1237
|
+
truncatedContent += htmlString.substring(0, index);
|
|
1238
|
+
}
|
|
1239
|
+
if (endResult[1] === '/') {
|
|
1240
|
+
tagsStack.pop();
|
|
1241
|
+
selfClose = null;
|
|
1242
|
+
}
|
|
1243
|
+
else {
|
|
1244
|
+
selfClose = SELF_CLOSE_REGEX.exec(endResult);
|
|
1245
|
+
if (!selfClose) {
|
|
1246
|
+
tag = matches[1];
|
|
1247
|
+
tagsStack.push(tag);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
truncatedContent += selfClose ? selfClose[0] : endResult;
|
|
1251
|
+
htmlString = htmlString.substring(index + endResult.length); // Update htmlString
|
|
1252
|
+
}
|
|
1253
|
+
if (htmlString.length > maxLength - totalLength && opts.ellipsis) {
|
|
1254
|
+
truncatedContent += opts.ellipsis ? '...' : '';
|
|
1255
|
+
}
|
|
1256
|
+
truncatedContent += closeTags(tagsStack);
|
|
1257
|
+
if (opts.removeImageTag) {
|
|
1258
|
+
truncatedContent = removeImageTag(truncatedContent);
|
|
1259
|
+
}
|
|
1260
|
+
return truncatedContent;
|
|
1261
|
+
}
|
|
1096
1262
|
/**
|
|
1097
1263
|
* Replaces text with a replacement based on a regex
|
|
1098
1264
|
* @param text - The text to replace
|