@redpanda-data/docs-extensions-and-macros 5.1.0 → 5.1.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.
@@ -292,40 +292,95 @@ function generateIndex (playbook, contentCatalog, { indexLatestOnly = false, exc
292
292
  ? page.asciidoc.attributes['page-commercial-names'].split(',').map(name => name.trim())
293
293
  : []
294
294
 
295
- // FIXED: keywords now included in index item
296
- const indexItem = {
297
- title: documentTitle,
298
- version: version,
299
- text: text,
300
- intro: intro,
301
- objectID: urlPath + page.pub.url,
302
- titles: titles,
303
- keywords: keywords,
304
- categories: categories,
305
- commercialNames: commercialNames,
306
- unixTimestamp: unixTimestamp
307
- }
295
+ // Algolia only indexes roughly the first ~290 words of any single record. On
296
+ // long reference pages (cluster/topic properties, metrics) every property or
297
+ // metric name lives in the `titles` array, and there can be hundreds of them, so
298
+ // every name past that window is stored but NOT searchable (DOC-1878). Split the
299
+ // titles of such pages across multiple records, each small enough to be fully
300
+ // indexed. Normal pages keep a single record. Chunks after the first get a unique
301
+ // objectID anchored to their first heading so search results deep-link into the
302
+ // page; the first chunk keeps the page's base objectID.
303
+ const baseObjectID = urlPath + page.pub.url
304
+ const titleChunks = isPropertiesPage ? chunkTitles(titles) : [titles]
305
+
306
+ titleChunks.forEach((chunk, chunkIndex) => {
307
+ const objectID = chunkIndex === 0
308
+ ? baseObjectID
309
+ : `${baseObjectID}#${chunk[0].h}`
310
+
311
+ // keywords included in index item
312
+ const indexItem = {
313
+ title: documentTitle,
314
+ version: version,
315
+ text: text,
316
+ intro: intro,
317
+ objectID: objectID,
318
+ // Clean page path (no #fragment) shared by every chunk of a page. The search
319
+ // UI builds result links from `url` and appends its own matched-heading
320
+ // anchor, so chunk objectIDs (which carry a #anchor for uniqueness) must not
321
+ // be used for the href. `url` is also the attribute to dedupe chunks on.
322
+ url: baseObjectID,
323
+ titles: chunk,
324
+ keywords: keywords,
325
+ categories: categories,
326
+ commercialNames: commercialNames,
327
+ unixTimestamp: unixTimestamp
328
+ }
308
329
 
309
- if (component.name !== 'labs') {
310
- indexItem.product = component.title
311
- indexItem.breadcrumbs = breadcrumbs
312
- indexItem.type = 'Doc'
313
- indexItem._tags = Array.isArray(tag) ? tag : [tag]
314
- } else {
315
- indexItem.deployment = deployment
316
- indexItem.type = 'Lab'
317
- indexItem.interactive = false
318
- indexItem._tags = Array.isArray(tag) ? tag : [tag]
319
- }
330
+ if (component.name !== 'labs') {
331
+ indexItem.product = component.title
332
+ indexItem.breadcrumbs = breadcrumbs
333
+ indexItem.type = 'Doc'
334
+ indexItem._tags = Array.isArray(tag) ? tag : [tag]
335
+ } else {
336
+ indexItem.deployment = deployment
337
+ indexItem.type = 'Lab'
338
+ indexItem.interactive = false
339
+ indexItem._tags = Array.isArray(tag) ? tag : [tag]
340
+ }
320
341
 
321
- algolia[cname][version].push(indexItem)
322
- algoliaCount++
342
+ algolia[cname][version].push(indexItem)
343
+ algoliaCount++
344
+ })
323
345
  }
324
346
 
325
347
  logger.info(`Indexed ${algoliaCount} pages`)
326
348
  return algolia
327
349
  }
328
350
 
351
+ /**
352
+ * Split a titles array into chunks whose combined token count stays within
353
+ * Algolia's per-record indexing window (~290 words; we leave a safety margin).
354
+ * Each returned chunk is small enough that every heading it contains is actually
355
+ * indexed for search, which keeps property/metric names on long reference pages
356
+ * searchable (DOC-1878).
357
+ *
358
+ * @param {Array<{t: string, h: string}>} titles - Section headings for a page.
359
+ * @param {number} [maxTokens=180] - Approximate token budget per chunk.
360
+ * @returns {Array<Array<{t: string, h: string}>>} One or more title chunks. An
361
+ * empty input yields a single empty chunk so the page still gets a record.
362
+ */
363
+ function chunkTitles (titles, maxTokens = 180) {
364
+ if (!titles.length) return [[]]
365
+ const chunks = []
366
+ let current = []
367
+ let tokens = 0
368
+ for (const entry of titles) {
369
+ // Algolia tokenizes on separators (whitespace, underscores, dots, etc.), so a
370
+ // name like `partition_autobalancing_mode` counts as several tokens.
371
+ const entryTokens = String(entry.t || '').split(/[\s_./:-]+/).filter(Boolean).length || 1
372
+ if (current.length && tokens + entryTokens > maxTokens) {
373
+ chunks.push(current)
374
+ current = []
375
+ tokens = 0
376
+ }
377
+ current.push(entry)
378
+ tokens += entryTokens
379
+ }
380
+ if (current.length) chunks.push(current)
381
+ return chunks
382
+ }
383
+
329
384
  /**
330
385
  * Extract the path from a URL
331
386
  * @param {string} url - The URL to extract path from
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "5.1.0",
3
+ "version": "5.1.1",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",