@mintlify/common 1.0.1137 → 1.0.1139

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/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export * from './asyncapi/index.js';
21
21
  export * from './isAbsoluteUrl.js';
22
22
  export * from './isValidEmail.js';
23
23
  export * from './rss/index.js';
24
+ export * from './search/index.js';
24
25
  export * from './services/aws.js';
25
26
  export * from './slugify.js';
26
27
  export * from './services/aws.js';
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ export * from './asyncapi/index.js';
21
21
  export * from './isAbsoluteUrl.js';
22
22
  export * from './isValidEmail.js';
23
23
  export * from './rss/index.js';
24
+ export * from './search/index.js';
24
25
  export * from './services/aws.js';
25
26
  export * from './slugify.js';
26
27
  export * from './services/aws.js';
@@ -3,4 +3,8 @@ import type { Root } from 'mdast';
3
3
  export declare function isStringSafe(value: string): boolean;
4
4
  export declare function filterStyleProperties(estree: Program): Program | null;
5
5
  export declare function rebuildStyleValue(properties: Property[]): string;
6
+ export declare const MAX_PURE_DATA_DEPTH = 32;
7
+ export declare function isPureDataExpression(value: unknown, depth?: number): boolean;
8
+ export declare function isPureDataProgram(estree: Program): boolean;
9
+ export declare function isUnsafeDataAttributeName(name: string): boolean;
6
10
  export declare function remarkMdxRemoveJs(): (tree: Root) => void;
@@ -211,6 +211,76 @@ export function rebuildStyleValue(properties) {
211
211
  });
212
212
  return `{ ${parts.join(', ')} }`;
213
213
  }
214
+ function isJsxLikeNode(value) {
215
+ return (typeof value === 'object' && value !== null && 'type' in value && typeof value.type === 'string');
216
+ }
217
+ const UNSAFE_OBJECT_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
218
+ export const MAX_PURE_DATA_DEPTH = 32;
219
+ function isPureDataLiteral(node) {
220
+ if (node.type !== 'Literal' || 'regex' in node || 'bigint' in node)
221
+ return false;
222
+ const value = node.value;
223
+ return (value === null ||
224
+ typeof value === 'string' ||
225
+ typeof value === 'number' ||
226
+ typeof value === 'boolean');
227
+ }
228
+ function pureDataObjectKey(node) {
229
+ if (!isJsxLikeNode(node))
230
+ return null;
231
+ if (node.type === 'Identifier' && typeof node.name === 'string')
232
+ return node.name;
233
+ if (node.type === 'Literal' && (typeof node.value === 'string' || typeof node.value === 'number'))
234
+ return String(node.value);
235
+ return null;
236
+ }
237
+ function isPureDataProperty(value, depth) {
238
+ if (!isJsxLikeNode(value) || value.type !== 'Property')
239
+ return false;
240
+ if (value.kind !== 'init' ||
241
+ value.method === true ||
242
+ value.computed === true ||
243
+ value.shorthand === true)
244
+ return false;
245
+ const key = pureDataObjectKey(value.key);
246
+ if (key === null || UNSAFE_OBJECT_KEYS.has(key))
247
+ return false;
248
+ return isPureDataExpression(value.value, depth);
249
+ }
250
+ export function isPureDataExpression(value, depth = 0) {
251
+ if (!isJsxLikeNode(value))
252
+ return false;
253
+ if (isPureDataLiteral(value))
254
+ return true;
255
+ if (value.type === 'UnaryExpression') {
256
+ return (value.operator === '-' &&
257
+ isJsxLikeNode(value.argument) &&
258
+ value.argument.type === 'Literal' &&
259
+ typeof value.argument.value === 'number');
260
+ }
261
+ if (value.type === 'TemplateLiteral') {
262
+ return (Array.isArray(value.expressions) &&
263
+ value.expressions.length === 0 &&
264
+ Array.isArray(value.quasis));
265
+ }
266
+ if (depth >= MAX_PURE_DATA_DEPTH)
267
+ return false;
268
+ if (value.type === 'ArrayExpression') {
269
+ return (Array.isArray(value.elements) &&
270
+ value.elements.every((element) => isPureDataExpression(element, depth + 1)));
271
+ }
272
+ if (value.type === 'ObjectExpression') {
273
+ return (Array.isArray(value.properties) &&
274
+ value.properties.every((property) => isPureDataProperty(property, depth + 1)));
275
+ }
276
+ return false;
277
+ }
278
+ export function isPureDataProgram(estree) {
279
+ const stmt = estree.body[0];
280
+ if (estree.body.length !== 1 || (stmt === null || stmt === void 0 ? void 0 : stmt.type) !== 'ExpressionStatement')
281
+ return false;
282
+ return isPureDataExpression(stmt.expression);
283
+ }
214
284
  function isPrimitiveLiteralExpression(node) {
215
285
  if (node.type === 'Literal')
216
286
  return true;
@@ -230,9 +300,7 @@ function isSafeJsxAttributeValue(value) {
230
300
  return true;
231
301
  if (node.type !== 'JSXExpressionContainer')
232
302
  return false;
233
- if (typeof node.expression !== 'object' || node.expression == null)
234
- return false;
235
- return isPrimitiveLiteralExpression(node.expression);
303
+ return isPureDataExpression(node.expression);
236
304
  }
237
305
  const DANGEROUS_ELEMENT_NAMES = new Set(['script', 'style', 'iframe', 'meta']);
238
306
  function isSafeJsxElementName(name) {
@@ -246,6 +314,9 @@ function isSafeJsxElementName(name) {
246
314
  return false;
247
315
  return !DANGEROUS_ELEMENT_NAMES.has(elementName.toLowerCase());
248
316
  }
317
+ export function isUnsafeDataAttributeName(name) {
318
+ return name === 'dangerouslySetInnerHTML' || /^on[A-Z]/.test(name);
319
+ }
249
320
  function isEventHandlerAttribute(attributeNode) {
250
321
  const name = attributeNode.name;
251
322
  if (typeof name !== 'object' || name == null)
@@ -253,7 +324,7 @@ function isEventHandlerAttribute(attributeNode) {
253
324
  const nameNode = name;
254
325
  if (typeof nameNode.name !== 'string')
255
326
  return false;
256
- return /^on[A-Z]/.test(nameNode.name);
327
+ return isUnsafeDataAttributeName(nameNode.name);
257
328
  }
258
329
  function isSafeStaticJsxNode(node) {
259
330
  if (node.type === 'JSXText')
@@ -463,14 +534,6 @@ function buildStubEsmNode(names) {
463
534
  },
464
535
  };
465
536
  }
466
- function isArrayOfStringLiterals(estree) {
467
- const stmt = estree.body[0];
468
- if (estree.body.length !== 1 || (stmt === null || stmt === void 0 ? void 0 : stmt.type) !== 'ExpressionStatement')
469
- return false;
470
- if (stmt.expression.type !== 'ArrayExpression')
471
- return false;
472
- return stmt.expression.elements.every((el) => (el === null || el === void 0 ? void 0 : el.type) === 'Literal' && typeof el.value === 'string');
473
- }
474
537
  export function remarkMdxRemoveJs() {
475
538
  return (tree) => {
476
539
  remove(tree, ['mdxTextExpression', 'mdxFlowExpression']);
@@ -505,7 +568,9 @@ export function remarkMdxRemoveJs() {
505
568
  stubbedNamesMap.set(ident, { name: ident, isFunction: false });
506
569
  return attr;
507
570
  }
508
- if (((_b = attr.value.data) === null || _b === void 0 ? void 0 : _b.estree) && isArrayOfStringLiterals(attr.value.data.estree))
571
+ if (!isUnsafeDataAttributeName(attr.name) &&
572
+ ((_b = attr.value.data) === null || _b === void 0 ? void 0 : _b.estree) &&
573
+ isPureDataProgram(attr.value.data.estree))
509
574
  return attr;
510
575
  if (isSafeStaticJsx((_c = attr.value.data) === null || _c === void 0 ? void 0 : _c.estree))
511
576
  return attr;
@@ -0,0 +1,7 @@
1
+ export declare function getHighlightedText(text: string, query: string): string;
2
+ export type HighlightSnippetOptions = {
3
+ leadingContext?: number;
4
+ maxLength?: number;
5
+ };
6
+ export declare function stripDuplicatedTitleLine(content: string, header: string): string;
7
+ export declare function getHighlightedSnippet(text: string, query: string, options?: HighlightSnippetOptions): string;
@@ -0,0 +1,400 @@
1
+ const STOP_WORDS = new Set([
2
+ 'i',
3
+ 'me',
4
+ 'my',
5
+ 'myself',
6
+ 'we',
7
+ 'our',
8
+ 'ours',
9
+ 'ourselves',
10
+ 'you',
11
+ 'your',
12
+ 'yours',
13
+ 'yourself',
14
+ 'yourselves',
15
+ 'he',
16
+ 'him',
17
+ 'his',
18
+ 'himself',
19
+ 'she',
20
+ 'her',
21
+ 'hers',
22
+ 'herself',
23
+ 'it',
24
+ 'its',
25
+ 'itself',
26
+ 'they',
27
+ 'them',
28
+ 'their',
29
+ 'theirs',
30
+ 'themselves',
31
+ 'what',
32
+ 'which',
33
+ 'who',
34
+ 'whom',
35
+ 'this',
36
+ 'that',
37
+ 'these',
38
+ 'those',
39
+ 'am',
40
+ 'is',
41
+ 'are',
42
+ 'was',
43
+ 'were',
44
+ 'be',
45
+ 'been',
46
+ 'being',
47
+ 'have',
48
+ 'has',
49
+ 'had',
50
+ 'having',
51
+ 'do',
52
+ 'does',
53
+ 'did',
54
+ 'doing',
55
+ 'a',
56
+ 'an',
57
+ 'the',
58
+ 'and',
59
+ 'but',
60
+ 'if',
61
+ 'or',
62
+ 'because',
63
+ 'as',
64
+ 'until',
65
+ 'while',
66
+ 'of',
67
+ 'at',
68
+ 'by',
69
+ 'for',
70
+ 'with',
71
+ 'about',
72
+ 'against',
73
+ 'between',
74
+ 'into',
75
+ 'through',
76
+ 'during',
77
+ 'before',
78
+ 'after',
79
+ 'above',
80
+ 'below',
81
+ 'to',
82
+ 'from',
83
+ 'up',
84
+ 'down',
85
+ 'in',
86
+ 'out',
87
+ 'on',
88
+ 'off',
89
+ 'over',
90
+ 'under',
91
+ 'again',
92
+ 'further',
93
+ 'then',
94
+ 'once',
95
+ 'here',
96
+ 'there',
97
+ 'when',
98
+ 'where',
99
+ 'why',
100
+ 'how',
101
+ 'all',
102
+ 'any',
103
+ 'both',
104
+ 'each',
105
+ 'few',
106
+ 'more',
107
+ 'most',
108
+ 'other',
109
+ 'some',
110
+ 'such',
111
+ 'no',
112
+ 'nor',
113
+ 'not',
114
+ 'only',
115
+ 'own',
116
+ 'same',
117
+ 'so',
118
+ 'than',
119
+ 'too',
120
+ 'very',
121
+ 's',
122
+ 't',
123
+ 'can',
124
+ 'will',
125
+ 'just',
126
+ 'don',
127
+ 'should',
128
+ 'now',
129
+ "aren't",
130
+ "it's",
131
+ "don't",
132
+ 'get',
133
+ ]);
134
+ const MAX_QUERY_WORDS = 20;
135
+ const HIGHLIGHT_TIMEOUT_MS = 100;
136
+ const PRE_TAG = '<mark><b>';
137
+ const POST_TAG = '</b></mark>';
138
+ function escapeHtml(str) {
139
+ return str
140
+ .replace(/&/g, '&amp;')
141
+ .replace(/</g, '&lt;')
142
+ .replace(/>/g, '&gt;')
143
+ .replace(/"/g, '&quot;');
144
+ }
145
+ // Server-side search results arrive pre-wrapped with <mark><b>…</b></mark>
146
+ // highlight tags. We re-highlight on the client now, so strip them here —
147
+ // otherwise escapeHtml below would turn them into visible &lt;mark&gt; text.
148
+ const SERVER_HIGHLIGHT_TAGS = /<\/?(?:mark|b)\b[^>]*>/gi;
149
+ function stripServerHighlightTags(text) {
150
+ return text.replace(SERVER_HIGHLIGHT_TAGS, '');
151
+ }
152
+ function isStopWord(word) {
153
+ return STOP_WORDS.has(word.toLowerCase());
154
+ }
155
+ function chunkByNonStopWords(words) {
156
+ const groups = [];
157
+ let current = [];
158
+ for (const w of words) {
159
+ if (!w)
160
+ continue;
161
+ if (isStopWord(w)) {
162
+ if (current.length > 0) {
163
+ groups.push(current);
164
+ current = [];
165
+ }
166
+ continue;
167
+ }
168
+ current.push(w);
169
+ }
170
+ if (current.length > 0)
171
+ groups.push(current);
172
+ return groups;
173
+ }
174
+ function findAllCaseInsensitive(text, search) {
175
+ const lower = text.toLowerCase();
176
+ const searchLower = search.toLowerCase();
177
+ const indices = [];
178
+ let start = 0;
179
+ while (start <= lower.length - searchLower.length) {
180
+ const idx = lower.indexOf(searchLower, start);
181
+ if (idx === -1)
182
+ break;
183
+ indices.push(idx);
184
+ start = idx + 1;
185
+ }
186
+ return indices;
187
+ }
188
+ function generateQueryVariations(query) {
189
+ const cleanedQuery = query.replace(/[?,.!\n"]/g, '');
190
+ const queryWords = cleanedQuery.split(/\s+/).filter(Boolean).slice(0, MAX_QUERY_WORDS);
191
+ const groups = chunkByNonStopWords(queryWords);
192
+ const multiToken = [];
193
+ for (const group of groups) {
194
+ if (group.length > 1)
195
+ multiToken.push(group.join(' '));
196
+ }
197
+ const groupStarts = groups
198
+ .map((g) => cleanedQuery.indexOf(g.join(' ')))
199
+ .filter((i) => i >= 0)
200
+ .sort((a, b) => a - b);
201
+ for (let i = 0; i < groupStarts.length - 1; i++) {
202
+ const span = cleanedQuery.slice(groupStarts[i], groupStarts[i + 1]).trim();
203
+ const nonStopCount = span.split(/\s+/).filter((w) => !isStopWord(w)).length;
204
+ if (nonStopCount > 1) {
205
+ if (span.split(/\s+/).length > 1)
206
+ multiToken.push(span);
207
+ }
208
+ }
209
+ if (groupStarts.length > 0) {
210
+ const tail = cleanedQuery.slice(groupStarts[groupStarts.length - 1]).trim();
211
+ if (tail)
212
+ multiToken.push(tail);
213
+ }
214
+ for (let len = queryWords.length - 1; len > 0; len--) {
215
+ for (let skip = 0; skip <= queryWords.length - len; skip++) {
216
+ const sub = queryWords
217
+ .slice(skip, skip + len)
218
+ .map((w) => w.trim())
219
+ .join(' ');
220
+ const nonStopCount = sub.split(/\s+/).filter((w) => !isStopWord(w)).length;
221
+ if (len > 2 || nonStopCount >= 1) {
222
+ multiToken.push(sub);
223
+ }
224
+ }
225
+ }
226
+ const variations = [query, cleanedQuery, ...multiToken].filter((x) => x.trim().length > 0);
227
+ const seen = new Set();
228
+ const unique = [];
229
+ for (const v of variations) {
230
+ if (!seen.has(v)) {
231
+ seen.add(v);
232
+ unique.push(v);
233
+ }
234
+ }
235
+ const numWords = queryWords.length;
236
+ unique.sort((a, b) => {
237
+ if (numWords >= MAX_QUERY_WORDS)
238
+ return b.trim().length - a.trim().length;
239
+ const aWords = a.split(/\s+/).filter(Boolean).length;
240
+ const bWords = b.split(/\s+/).filter(Boolean).length;
241
+ if (bWords !== aWords)
242
+ return bWords - aWords;
243
+ return b.trim().length - a.trim().length;
244
+ });
245
+ return unique;
246
+ }
247
+ function computeMergedSpans(text, matchedQueries) {
248
+ var _a;
249
+ const spans = [];
250
+ const isWordChar = /\w/;
251
+ for (const q of matchedQueries) {
252
+ const trimmed = q.trim();
253
+ if (!trimmed)
254
+ continue;
255
+ const isSingleWord = !/\s/.test(trimmed);
256
+ for (const idx of findAllCaseInsensitive(text, trimmed)) {
257
+ const end = idx + trimmed.length;
258
+ if (isSingleWord) {
259
+ const beforeOk = idx === 0 || !isWordChar.test((_a = text[idx - 1]) !== null && _a !== void 0 ? _a : '');
260
+ if (!beforeOk)
261
+ continue;
262
+ }
263
+ spans.push({ start: idx, end });
264
+ }
265
+ }
266
+ spans.sort((a, b) => a.start - b.start || b.end - a.end);
267
+ const mergedSpans = [];
268
+ for (const span of spans) {
269
+ const last = mergedSpans[mergedSpans.length - 1];
270
+ if (last && span.start <= last.end) {
271
+ last.end = Math.max(last.end, span.end);
272
+ }
273
+ else {
274
+ mergedSpans.push({ start: span.start, end: span.end });
275
+ }
276
+ }
277
+ return mergedSpans;
278
+ }
279
+ function renderSpans(text, mergedSpans) {
280
+ let result = '';
281
+ let cursor = 0;
282
+ for (const span of mergedSpans) {
283
+ result += escapeHtml(text.slice(cursor, span.start));
284
+ const original = text.slice(span.start, span.end);
285
+ if (original)
286
+ result += `${PRE_TAG}${escapeHtml(original)}${POST_TAG}`;
287
+ cursor = span.end;
288
+ }
289
+ result += escapeHtml(text.slice(cursor));
290
+ return result;
291
+ }
292
+ function renderHighlightedSpans(text, matchedQueries) {
293
+ return renderSpans(text, computeMergedSpans(text, matchedQueries));
294
+ }
295
+ const VARIATIONS_CACHE_LIMIT = 64;
296
+ const variationsCache = new Map();
297
+ function getCachedVariations(query) {
298
+ const cached = variationsCache.get(query);
299
+ if (cached)
300
+ return cached;
301
+ const variations = generateQueryVariations(query);
302
+ variationsCache.set(query, variations);
303
+ if (variationsCache.size > VARIATIONS_CACHE_LIMIT) {
304
+ const oldestKey = variationsCache.keys().next().value;
305
+ if (oldestKey !== undefined)
306
+ variationsCache.delete(oldestKey);
307
+ }
308
+ return variations;
309
+ }
310
+ function getMatchedQueries(text, query) {
311
+ const deadline = Date.now() + HIGHLIGHT_TIMEOUT_MS;
312
+ const variations = getCachedVariations(query);
313
+ const matchedQueries = [];
314
+ for (const variation of variations) {
315
+ if (Date.now() > deadline)
316
+ return [];
317
+ const trimmed = variation.trim();
318
+ if (matchedQueries.some((q) => q.trim() === trimmed))
319
+ continue;
320
+ if (findAllCaseInsensitive(text, variation).length > 0) {
321
+ matchedQueries.push(variation);
322
+ }
323
+ }
324
+ return matchedQueries;
325
+ }
326
+ export function getHighlightedText(text, query) {
327
+ const stripped = stripServerHighlightTags(text);
328
+ if (!stripped || !query)
329
+ return escapeHtml(stripped);
330
+ const matchedQueries = getMatchedQueries(stripped, query);
331
+ if (matchedQueries.length === 0)
332
+ return escapeHtml(stripped);
333
+ return renderHighlightedSpans(stripped, matchedQueries);
334
+ }
335
+ const ELLIPSIS = '…';
336
+ const SNIPPET_LEADING_CONTEXT = 24;
337
+ const SNIPPET_MAX_LENGTH = 240;
338
+ const SNIPPET_BOUNDARY_LOOKAROUND = 12;
339
+ function snapStartToWordBoundary(text, index, lookaround) {
340
+ if (index <= 0)
341
+ return 0;
342
+ const earliest = Math.max(0, index - lookaround);
343
+ const previousSpace = text.lastIndexOf(' ', index - 1);
344
+ return previousSpace >= earliest ? previousSpace + 1 : index;
345
+ }
346
+ function snapEndToWordBoundary(text, index, lookaround) {
347
+ if (index >= text.length)
348
+ return text.length;
349
+ const latest = Math.min(text.length, index + lookaround);
350
+ const nextSpace = text.indexOf(' ', index);
351
+ return nextSpace !== -1 && nextSpace < latest ? nextSpace : index;
352
+ }
353
+ function leadingWindow(text, maxLength) {
354
+ if (text.length <= maxLength)
355
+ return escapeHtml(text);
356
+ const end = snapEndToWordBoundary(text, maxLength, SNIPPET_BOUNDARY_LOOKAROUND);
357
+ return `${escapeHtml(text.slice(0, end))} ${ELLIPSIS}`;
358
+ }
359
+ export function stripDuplicatedTitleLine(content, header) {
360
+ const newlineIdx = content.indexOf('\n');
361
+ if (newlineIdx < 0 || !header)
362
+ return content;
363
+ const normalize = (value) => stripServerHighlightTags(value).replace(/\s+/g, ' ').trim();
364
+ return normalize(content.slice(0, newlineIdx)) === normalize(header)
365
+ ? content.slice(newlineIdx + 1)
366
+ : content;
367
+ }
368
+ export function getHighlightedSnippet(text, query, options) {
369
+ var _a, _b;
370
+ const stripped = stripServerHighlightTags(text);
371
+ const leadingContext = (_a = options === null || options === void 0 ? void 0 : options.leadingContext) !== null && _a !== void 0 ? _a : SNIPPET_LEADING_CONTEXT;
372
+ const maxLength = (_b = options === null || options === void 0 ? void 0 : options.maxLength) !== null && _b !== void 0 ? _b : SNIPPET_MAX_LENGTH;
373
+ if (!stripped || !query)
374
+ return leadingWindow(stripped, maxLength);
375
+ const matchedQueries = getMatchedQueries(stripped, query);
376
+ if (matchedQueries.length === 0)
377
+ return leadingWindow(stripped, maxLength);
378
+ const mergedSpans = computeMergedSpans(stripped, matchedQueries);
379
+ const firstMatch = mergedSpans[0];
380
+ if (!firstMatch)
381
+ return leadingWindow(stripped, maxLength);
382
+ let windowStart = firstMatch.start <= leadingContext ? 0 : firstMatch.start - leadingContext;
383
+ let windowEnd = Math.min(stripped.length, Math.max(firstMatch.end, windowStart + maxLength));
384
+ windowStart = snapStartToWordBoundary(stripped, windowStart, SNIPPET_BOUNDARY_LOOKAROUND);
385
+ windowEnd = snapEndToWordBoundary(stripped, windowEnd, SNIPPET_BOUNDARY_LOOKAROUND);
386
+ const windowed = stripped.slice(windowStart, windowEnd);
387
+ const windowedSpans = [];
388
+ for (const span of mergedSpans) {
389
+ if (span.end <= windowStart || span.start >= windowEnd)
390
+ continue;
391
+ windowedSpans.push({
392
+ start: Math.max(0, span.start - windowStart),
393
+ end: Math.min(windowed.length, span.end - windowStart),
394
+ });
395
+ }
396
+ const body = renderSpans(windowed, windowedSpans);
397
+ const lead = windowStart > 0 ? `${ELLIPSIS} ` : '';
398
+ const trail = windowEnd < stripped.length ? ` ${ELLIPSIS}` : '';
399
+ return `${lead}${body}${trail}`;
400
+ }
@@ -0,0 +1 @@
1
+ export {};