@nbtca/docs 0.3.0 → 0.3.1
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/client.js +43 -4
- package/dist/content.js +240 -55
- package/package.json +2 -2
package/dist/client.js
CHANGED
|
@@ -165,9 +165,45 @@ function cacheTtl(value, fallback, name) {
|
|
|
165
165
|
function isTransientStatus(status) {
|
|
166
166
|
return status === 408 || status === 429 || status >= 500;
|
|
167
167
|
}
|
|
168
|
+
function isSecondaryRateLimitMessage(value) {
|
|
169
|
+
if (!isRecord(value) || typeof value.message !== 'string')
|
|
170
|
+
return false;
|
|
171
|
+
const message = value.message.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
172
|
+
return (/\b(?:exceeded|hit|triggered) (?:a |the )?secondary rate limit\b/.test(message) ||
|
|
173
|
+
/\bsecondary rate limit (?:was |has been )?(?:exceeded|hit|triggered)\b/.test(message));
|
|
174
|
+
}
|
|
175
|
+
async function isTransientResponse(response) {
|
|
176
|
+
if (isTransientStatus(response.status))
|
|
177
|
+
return true;
|
|
178
|
+
if (response.status !== 403)
|
|
179
|
+
return false;
|
|
180
|
+
if (response.headers.get('x-ratelimit-remaining') === '0' ||
|
|
181
|
+
response.headers.get('retry-after') !== null) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
return isSecondaryRateLimitMessage(await response.clone().json());
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
168
191
|
function reject(error) {
|
|
169
192
|
return Promise.reject(error instanceof Error ? error : new Error('Operation failed with a non-error value'));
|
|
170
193
|
}
|
|
194
|
+
function cancelUnusedResponseBody(response) {
|
|
195
|
+
try {
|
|
196
|
+
if (!response || response.bodyUsed)
|
|
197
|
+
return;
|
|
198
|
+
const body = response.body;
|
|
199
|
+
if (!body)
|
|
200
|
+
return;
|
|
201
|
+
void body.cancel().catch(() => undefined);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Cleanup must not override the request result.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
171
207
|
function isRecord(value) {
|
|
172
208
|
return typeof value === 'object' && value !== null;
|
|
173
209
|
}
|
|
@@ -231,12 +267,15 @@ export function createDocsClient(options = {}) {
|
|
|
231
267
|
const timer = setTimeout(() => {
|
|
232
268
|
ctrl.abort();
|
|
233
269
|
}, timeoutMs);
|
|
270
|
+
let response;
|
|
234
271
|
try {
|
|
235
|
-
|
|
272
|
+
response = await fetch(url, { signal: ctrl.signal, headers: headers() });
|
|
236
273
|
return await consume(response);
|
|
237
274
|
}
|
|
238
275
|
finally {
|
|
239
276
|
clearTimeout(timer);
|
|
277
|
+
// Not awaited: a custom transport's cancel may never settle.
|
|
278
|
+
cancelUnusedResponseBody(response);
|
|
240
279
|
}
|
|
241
280
|
}
|
|
242
281
|
function recoverFailure(cache, key, path, error, copy = (value) => value) {
|
|
@@ -265,7 +304,7 @@ export function createDocsClient(options = {}) {
|
|
|
265
304
|
return await withResponse(url, 10000, async (response) => {
|
|
266
305
|
if (!response.ok) {
|
|
267
306
|
const stale = dirCache.getStale(path);
|
|
268
|
-
if (
|
|
307
|
+
if (stale !== undefined && (await isTransientResponse(response)))
|
|
269
308
|
return copyItems(stale);
|
|
270
309
|
throw new DocsFetchError(path, response.status, `HTTP ${String(response.status)}`);
|
|
271
310
|
}
|
|
@@ -301,7 +340,7 @@ export function createDocsClient(options = {}) {
|
|
|
301
340
|
return await withResponse(url, 20000, async (response) => {
|
|
302
341
|
if (!response.ok) {
|
|
303
342
|
const stale = treeCache.getStale(key);
|
|
304
|
-
if (
|
|
343
|
+
if (stale !== undefined && (await isTransientResponse(response)))
|
|
305
344
|
return copyItems(stale);
|
|
306
345
|
throw new DocsFetchError('', response.status, `HTTP ${String(response.status)}`);
|
|
307
346
|
}
|
|
@@ -340,7 +379,7 @@ export function createDocsClient(options = {}) {
|
|
|
340
379
|
return await withResponse(url, 15000, async (response) => {
|
|
341
380
|
if (!response.ok) {
|
|
342
381
|
const stale = fileCache.getStale(path);
|
|
343
|
-
if (
|
|
382
|
+
if (stale !== undefined && (await isTransientResponse(response)))
|
|
344
383
|
return stale;
|
|
345
384
|
throw new DocsFetchError(path, response.status, `HTTP ${String(response.status)}`);
|
|
346
385
|
}
|
package/dist/content.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
const SUMMARY_LENGTH = 160;
|
|
2
2
|
const EXCERPT_LENGTH = 180;
|
|
3
|
+
const GRAPHEME_SEGMENTER = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
4
|
+
const LIST_ITEM = /^( {0,3}(?:[-+*]|\d{1,9}[.)]))([ \t]+)/;
|
|
5
|
+
const NORMALIZATION_CHUNK = 512;
|
|
6
|
+
// NFKC never joins these characters to what precedes them, so chunks split before them.
|
|
7
|
+
const NORMALIZATION_BOUNDARY = /[ -~\u4e00-\u9fff]/g;
|
|
3
8
|
const COMPONENT_ATTRIBUTES = {
|
|
4
9
|
Band: ['alt', 'source'],
|
|
5
10
|
Figure: ['alt', 'caption', 'date', 'source'],
|
|
@@ -9,11 +14,12 @@ const COMPONENT_ATTRIBUTES = {
|
|
|
9
14
|
TimelineEntry: ['year', 'title'],
|
|
10
15
|
};
|
|
11
16
|
function splitFrontmatter(content) {
|
|
12
|
-
const
|
|
17
|
+
const source = content.startsWith('\uFEFF') ? content.slice(1) : content;
|
|
18
|
+
const match = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(source);
|
|
13
19
|
if (!match)
|
|
14
|
-
return { body:
|
|
20
|
+
return { body: source, frontmatter: '' };
|
|
15
21
|
return {
|
|
16
|
-
body:
|
|
22
|
+
body: source.slice(match[0].length),
|
|
17
23
|
frontmatter: match[1] ?? '',
|
|
18
24
|
};
|
|
19
25
|
}
|
|
@@ -57,18 +63,113 @@ function cleanInline(value) {
|
|
|
57
63
|
.replace(/\s+/g, ' ')
|
|
58
64
|
.trim();
|
|
59
65
|
}
|
|
60
|
-
function
|
|
66
|
+
function quoteContainer(line) {
|
|
67
|
+
let depth = 0;
|
|
68
|
+
let rest = line;
|
|
69
|
+
for (;;) {
|
|
70
|
+
const prefix = /^ {0,3}>[ \t]?/.exec(rest)?.[0];
|
|
71
|
+
if (!prefix)
|
|
72
|
+
return { depth, rest };
|
|
73
|
+
depth += 1;
|
|
74
|
+
rest = rest.slice(prefix.length);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function transitionFence(line, current) {
|
|
78
|
+
const quote = quoteContainer(line);
|
|
79
|
+
let candidate = quote.rest;
|
|
80
|
+
let listIndent = 0;
|
|
81
|
+
if (current) {
|
|
82
|
+
if (quote.depth < current.quoteDepth)
|
|
83
|
+
return transitionFence(line, undefined);
|
|
84
|
+
if (quote.depth !== current.quoteDepth)
|
|
85
|
+
return { delimiter: false, fence: current };
|
|
86
|
+
if (current.listIndent > 0) {
|
|
87
|
+
const indentation = /^ */.exec(candidate)?.[0].length ?? 0;
|
|
88
|
+
if (candidate.trim() !== '' && indentation < current.listIndent) {
|
|
89
|
+
return transitionFence(line, undefined);
|
|
90
|
+
}
|
|
91
|
+
candidate = candidate.slice(Math.min(indentation, current.listIndent));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
listIndent = LIST_ITEM.exec(candidate)?.[0].length ?? 0;
|
|
96
|
+
candidate = candidate.slice(listIndent);
|
|
97
|
+
}
|
|
98
|
+
const match = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(candidate);
|
|
99
|
+
const sequence = match?.[1];
|
|
100
|
+
if (!sequence)
|
|
101
|
+
return { delimiter: false, fence: current };
|
|
102
|
+
const marker = sequence.startsWith('`') ? '`' : '~';
|
|
103
|
+
const suffix = match[2] ?? '';
|
|
104
|
+
if (!current) {
|
|
105
|
+
if (marker === '`' && suffix.includes('`')) {
|
|
106
|
+
return { delimiter: false, fence: undefined };
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
delimiter: true,
|
|
110
|
+
fence: { length: sequence.length, listIndent, marker, quoteDepth: quote.depth },
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (marker === current.marker && sequence.length >= current.length && /^[ \t]*$/.test(suffix)) {
|
|
114
|
+
return { delimiter: true, fence: undefined };
|
|
115
|
+
}
|
|
116
|
+
return { delimiter: false, fence: current };
|
|
117
|
+
}
|
|
118
|
+
function indentWidth(line) {
|
|
119
|
+
let width = 0;
|
|
120
|
+
for (const character of line) {
|
|
121
|
+
if (character === ' ')
|
|
122
|
+
width += 1;
|
|
123
|
+
else if (character === '\t')
|
|
124
|
+
width += 4 - (width % 4);
|
|
125
|
+
else
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
return width;
|
|
129
|
+
}
|
|
130
|
+
function proseLines(body) {
|
|
61
131
|
let fence;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
132
|
+
let afterBreak = true;
|
|
133
|
+
let inCode = false;
|
|
134
|
+
let listIndent = 0;
|
|
135
|
+
return body.split(/\r?\n/).map((line) => {
|
|
136
|
+
const transition = transitionFence(line, fence);
|
|
137
|
+
fence = transition.fence;
|
|
138
|
+
if (transition.delimiter || fence) {
|
|
139
|
+
afterBreak = true;
|
|
140
|
+
inCode = false;
|
|
141
|
+
return undefined;
|
|
70
142
|
}
|
|
71
|
-
|
|
143
|
+
const candidate = quoteContainer(line).rest;
|
|
144
|
+
if (candidate.trim() === '') {
|
|
145
|
+
afterBreak = true;
|
|
146
|
+
return line;
|
|
147
|
+
}
|
|
148
|
+
const width = indentWidth(candidate);
|
|
149
|
+
if (afterBreak && width < listIndent)
|
|
150
|
+
listIndent = 0;
|
|
151
|
+
inCode = width - listIndent >= 4 && (afterBreak || inCode);
|
|
152
|
+
afterBreak = false;
|
|
153
|
+
if (inCode)
|
|
154
|
+
return undefined;
|
|
155
|
+
const item = LIST_ITEM.exec(candidate);
|
|
156
|
+
if (!item)
|
|
157
|
+
return line;
|
|
158
|
+
const marker = item[1]?.length ?? 0;
|
|
159
|
+
const padding = item[2] ?? '';
|
|
160
|
+
const rest = candidate.slice(item[0].length);
|
|
161
|
+
if (padding.includes('\t') || padding.length >= 5 || /^(?: {4}|\t)/.test(rest)) {
|
|
162
|
+
listIndent = marker + 1;
|
|
163
|
+
inCode = true;
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
listIndent = marker + padding.length;
|
|
167
|
+
return line;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function extractTitle(body) {
|
|
171
|
+
for (const line of proseLines(body)) {
|
|
172
|
+
if (line === undefined)
|
|
72
173
|
continue;
|
|
73
174
|
const match = /^#\s+(.+?)\s*$/.exec(line);
|
|
74
175
|
if (match?.[1])
|
|
@@ -77,17 +178,19 @@ function extractTitle(body) {
|
|
|
77
178
|
return undefined;
|
|
78
179
|
}
|
|
79
180
|
function truncate(value, length) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
181
|
+
let end = 0;
|
|
182
|
+
let codePoints = 0;
|
|
183
|
+
for (const { index, segment } of GRAPHEME_SEGMENTER.segment(value)) {
|
|
184
|
+
codePoints += Array.from(segment).length;
|
|
185
|
+
if (codePoints > length)
|
|
186
|
+
return `${value.slice(0, end).trimEnd()}…`;
|
|
187
|
+
end = index + segment.length;
|
|
188
|
+
}
|
|
189
|
+
return value;
|
|
86
190
|
}
|
|
87
191
|
function extractSummary(body) {
|
|
88
192
|
const paragraphs = [];
|
|
89
193
|
let current = [];
|
|
90
|
-
let fence;
|
|
91
194
|
let inContainer = false;
|
|
92
195
|
let inTag = false;
|
|
93
196
|
let hiddenTag;
|
|
@@ -96,19 +199,12 @@ function extractSummary(body) {
|
|
|
96
199
|
paragraphs.push(current.join(' '));
|
|
97
200
|
current = [];
|
|
98
201
|
};
|
|
99
|
-
for (const rawLine of body
|
|
100
|
-
|
|
101
|
-
const marker = /^(`{3,}|~{3,})/.exec(line)?.[1]?.slice(0, 1);
|
|
102
|
-
if (marker) {
|
|
103
|
-
if (!fence)
|
|
104
|
-
fence = marker;
|
|
105
|
-
else if (marker === fence)
|
|
106
|
-
fence = undefined;
|
|
202
|
+
for (const rawLine of proseLines(body)) {
|
|
203
|
+
if (rawLine === undefined) {
|
|
107
204
|
finishParagraph();
|
|
108
205
|
continue;
|
|
109
206
|
}
|
|
110
|
-
|
|
111
|
-
continue;
|
|
207
|
+
const line = rawLine.trim();
|
|
112
208
|
if (hiddenTag) {
|
|
113
209
|
if (line.toLowerCase().includes(`</${hiddenTag}>`))
|
|
114
210
|
hiddenTag = undefined;
|
|
@@ -175,21 +271,10 @@ function parseAttributes(source) {
|
|
|
175
271
|
return attributes;
|
|
176
272
|
}
|
|
177
273
|
function componentSource(body) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (marker) {
|
|
183
|
-
if (!fence)
|
|
184
|
-
fence = marker;
|
|
185
|
-
else if (marker === fence)
|
|
186
|
-
fence = undefined;
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
if (!fence)
|
|
190
|
-
lines.push(line);
|
|
191
|
-
}
|
|
192
|
-
return lines.join('\n').replace(/<!--[\s\S]*?-->/g, ' ');
|
|
274
|
+
return proseLines(body)
|
|
275
|
+
.filter((line) => line !== undefined)
|
|
276
|
+
.join('\n')
|
|
277
|
+
.replace(/<!--[\s\S]*?-->/g, ' ');
|
|
193
278
|
}
|
|
194
279
|
function extractComponents(body) {
|
|
195
280
|
const components = [];
|
|
@@ -258,7 +343,75 @@ export function parseDoc(path, content) {
|
|
|
258
343
|
};
|
|
259
344
|
}
|
|
260
345
|
function normalize(value) {
|
|
261
|
-
|
|
346
|
+
// Lowercasing keeps final sigma (ς) where a search for Σ yields σ.
|
|
347
|
+
return value
|
|
348
|
+
.normalize('NFKC')
|
|
349
|
+
.toLowerCase()
|
|
350
|
+
.replace(/\u03c2/g, '\u03c3');
|
|
351
|
+
}
|
|
352
|
+
function normalizeChunks(text) {
|
|
353
|
+
const offsets = [];
|
|
354
|
+
const sources = [];
|
|
355
|
+
const parts = [];
|
|
356
|
+
let length = 0;
|
|
357
|
+
for (let start = 0; start < text.length;) {
|
|
358
|
+
NORMALIZATION_BOUNDARY.lastIndex = start + NORMALIZATION_CHUNK;
|
|
359
|
+
const end = NORMALIZATION_BOUNDARY.exec(text)?.index ?? text.length;
|
|
360
|
+
const part = normalize(text.slice(start, end));
|
|
361
|
+
offsets.push(length);
|
|
362
|
+
sources.push(start);
|
|
363
|
+
parts.push(part);
|
|
364
|
+
length += part.length;
|
|
365
|
+
start = end;
|
|
366
|
+
}
|
|
367
|
+
return { offsets, sources, value: parts.join('') };
|
|
368
|
+
}
|
|
369
|
+
function lastIndexAtMost(count, target, valueAt) {
|
|
370
|
+
let low = 0;
|
|
371
|
+
let high = count - 1;
|
|
372
|
+
while (low < high) {
|
|
373
|
+
const middle = Math.ceil((low + high) / 2);
|
|
374
|
+
if (valueAt(middle) <= target)
|
|
375
|
+
low = middle;
|
|
376
|
+
else
|
|
377
|
+
high = middle - 1;
|
|
378
|
+
}
|
|
379
|
+
return low;
|
|
380
|
+
}
|
|
381
|
+
function graphemeAt(graphemes, position) {
|
|
382
|
+
const part = graphemes.containing(position);
|
|
383
|
+
if (!part)
|
|
384
|
+
return { codePoints: 0, end: position, start: position };
|
|
385
|
+
return {
|
|
386
|
+
codePoints: Array.from(part.segment).length,
|
|
387
|
+
end: part.index + part.segment.length,
|
|
388
|
+
start: part.index,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function normalizedLength(text, normalized, position) {
|
|
392
|
+
const { offsets, sources } = normalized;
|
|
393
|
+
const chunk = lastIndexAtMost(sources.length, position, (index) => sources[index] ?? 0);
|
|
394
|
+
const start = sources[chunk] ?? 0;
|
|
395
|
+
return (offsets[chunk] ?? 0) + normalize(text.slice(start, position)).length;
|
|
396
|
+
}
|
|
397
|
+
function sourceBoundary(text, normalized, graphemes, offset) {
|
|
398
|
+
const { offsets, sources } = normalized;
|
|
399
|
+
const chunk = lastIndexAtMost(offsets.length, offset, (index) => offsets[index] ?? 0);
|
|
400
|
+
const chunkEnd = sources[chunk + 1] ?? text.length;
|
|
401
|
+
const boundaries = [graphemeAt(graphemes, sources[chunk] ?? 0).start];
|
|
402
|
+
for (let position = boundaries[0] ?? 0; position < chunkEnd;) {
|
|
403
|
+
position = graphemeAt(graphemes, position).end;
|
|
404
|
+
boundaries.push(position);
|
|
405
|
+
}
|
|
406
|
+
const lengths = new Map();
|
|
407
|
+
const lengthAt = (index) => {
|
|
408
|
+
const position = boundaries[index] ?? 0;
|
|
409
|
+
const length = lengths.get(position) ?? normalizedLength(text, normalized, position);
|
|
410
|
+
lengths.set(position, length);
|
|
411
|
+
return length;
|
|
412
|
+
};
|
|
413
|
+
const index = lastIndexAtMost(boundaries.length, offset, lengthAt);
|
|
414
|
+
return { length: lengthAt(index), position: boundaries[index] ?? 0 };
|
|
262
415
|
}
|
|
263
416
|
function countMatches(value, term) {
|
|
264
417
|
let count = 0;
|
|
@@ -275,18 +428,50 @@ function countMatches(value, term) {
|
|
|
275
428
|
function excerpt(text, query, terms) {
|
|
276
429
|
if (!text)
|
|
277
430
|
return '';
|
|
278
|
-
const normalized =
|
|
279
|
-
const exactIndex = normalized.indexOf(query);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
431
|
+
const normalized = normalizeChunks(text);
|
|
432
|
+
const exactIndex = normalized.value.indexOf(query);
|
|
433
|
+
let matchIndex = exactIndex;
|
|
434
|
+
let matchLength = query.length;
|
|
435
|
+
if (matchIndex < 0) {
|
|
436
|
+
matchIndex = Number.POSITIVE_INFINITY;
|
|
437
|
+
for (const term of terms) {
|
|
438
|
+
const index = normalized.value.indexOf(term);
|
|
439
|
+
if (index >= 0 && index < matchIndex) {
|
|
440
|
+
matchIndex = index;
|
|
441
|
+
matchLength = term.length;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
283
445
|
if (!Number.isFinite(matchIndex))
|
|
284
446
|
return truncate(text, EXCERPT_LENGTH);
|
|
285
|
-
const
|
|
447
|
+
const graphemes = GRAPHEME_SEGMENTER.segment(text);
|
|
448
|
+
const matchStart = sourceBoundary(text, normalized, graphemes, matchIndex).position;
|
|
449
|
+
const endOffset = Math.min(normalized.value.length, matchIndex + matchLength);
|
|
450
|
+
const floor = sourceBoundary(text, normalized, graphemes, endOffset);
|
|
451
|
+
const matchEnd = floor.length < endOffset ? graphemeAt(graphemes, floor.position).end : floor.position;
|
|
452
|
+
const matchCodePoints = Array.from(text.slice(matchStart, matchEnd)).length;
|
|
453
|
+
const contextLimit = Math.min(Math.floor(EXCERPT_LENGTH / 3), Math.max(0, EXCERPT_LENGTH - matchCodePoints));
|
|
454
|
+
let start = matchStart;
|
|
455
|
+
let contextCodePoints = 0;
|
|
456
|
+
while (start > 0) {
|
|
457
|
+
const previous = graphemeAt(graphemes, start - 1);
|
|
458
|
+
if (contextCodePoints + previous.codePoints > contextLimit)
|
|
459
|
+
break;
|
|
460
|
+
contextCodePoints += previous.codePoints;
|
|
461
|
+
start = previous.start;
|
|
462
|
+
}
|
|
463
|
+
let end = start;
|
|
464
|
+
let selectedCodePoints = 0;
|
|
465
|
+
while (end < text.length) {
|
|
466
|
+
const next = graphemeAt(graphemes, end);
|
|
467
|
+
if (end > start && selectedCodePoints + next.codePoints > EXCERPT_LENGTH)
|
|
468
|
+
break;
|
|
469
|
+
selectedCodePoints += next.codePoints;
|
|
470
|
+
end = next.end;
|
|
471
|
+
}
|
|
286
472
|
const prefix = start > 0 ? '…' : '';
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
return `${prefix}${value}${suffix}`;
|
|
473
|
+
const suffix = end < text.length ? '…' : '';
|
|
474
|
+
return `${prefix}${text.slice(start, end).trim()}${suffix}`;
|
|
290
475
|
}
|
|
291
476
|
export function searchDoc(page, query) {
|
|
292
477
|
const normalizedQuery = normalize(query.trim());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nbtca/docs",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "GitHub-backed document client for NBTCA",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,6 +60,6 @@
|
|
|
60
60
|
"typescript": "^5.9.3",
|
|
61
61
|
"typescript-eslint": "^8.67.0",
|
|
62
62
|
"vite": "6.4.3",
|
|
63
|
-
"vitest": "^
|
|
63
|
+
"vitest": "^4.1.11"
|
|
64
64
|
}
|
|
65
65
|
}
|