docusaurus-plugin-glossary 3.3.2 → 3.3.4

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/README.md CHANGED
@@ -12,7 +12,7 @@ A comprehensive Docusaurus plugin that provides glossary functionality with an a
12
12
  ## Features
13
13
 
14
14
  - **Auto-generated Glossary Page**: Displays all terms alphabetically with letter navigation
15
- - **Search Functionality**: Real-time search across terms and definitions
15
+ - **Search Functionality**: Real-time search across terms, definitions, abbreviations, and aliases
16
16
  - **GlossaryTerm Component**: Inline component for linking terms with tooltip previews
17
17
  - **Automatic Term Detection**: Automatically detect and link glossary terms in markdown files with tooltips
18
18
  - **Responsive Design**: Mobile-friendly UI with dark mode support
@@ -381,7 +381,7 @@ The glossary page is automatically available at `/glossary` (or your configured
381
381
  **Features:**
382
382
 
383
383
  - Alphabetical grouping with letter navigation
384
- - Real-time search across terms and definitions
384
+ - Real-time search across terms, definitions, abbreviations, and aliases
385
385
  - Clickable related terms
386
386
  - Responsive design
387
387
  - Dark mode support
@@ -405,10 +405,26 @@ module.exports = {
405
405
 
406
406
  ## Configuration Options
407
407
 
408
- | Option | Type | Default | Description |
409
- | -------------- | ------ | -------------------------- | ----------------------------------------------------- |
410
- | `glossaryPath` | string | `'glossary/glossary.json'` | Path to glossary JSON file relative to site directory |
411
- | `routePath` | string | `'/glossary'` | URL path for glossary page |
408
+ | Option | Type | Default | Description |
409
+ | -------------------------- | ------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
410
+ | `glossaryPath` | string | `'glossary/glossary.json'` | Path to glossary JSON file relative to site directory |
411
+ | `routePath` | string | `'/glossary'` | URL path for glossary page |
412
+ | `expandAcronymsOnFirstUse` | boolean | `false` | When `true`, expand the first canonical occurrence of any term that has an `abbreviation` to "Long Form (Term)" per file |
413
+
414
+ ### Auto-expanding acronyms on first use
415
+
416
+ When `expandAcronymsOnFirstUse` is enabled, the remark plugin rewrites the first canonical occurrence of any term that has an `abbreviation` so that the long form is introduced inline. For example, given a term `{ "term": "PSP", "abbreviation": "Payment Service Provider", ... }`:
417
+
418
+ - Source: `The PSP charges a fee. Each PSP has its own pricing.`
419
+ - Rendered: `The Payment Service Provider (PSP) charges a fee. Each PSP has its own pricing.`
420
+
421
+ Rules:
422
+
423
+ - Only the canonical `term` triggers expansion — aliases and plural forms (`PSPs`) do not.
424
+ - Expansion only fires when the term has an `abbreviation` field.
425
+ - If the author already wrote the long form immediately before the term (e.g. they typed `Payment Service Provider (PSP)` themselves), expansion is skipped to avoid duplication.
426
+ - Scope is per file — the first occurrence in each markdown/MDX file is expanded.
427
+ - Terms with `autoLink: false` are not expanded.
412
428
 
413
429
  ## Customization
414
430
 
@@ -597,6 +613,7 @@ The remark plugin (`remark/glossary-terms.js`) automatically detects glossary te
597
613
  - Skips terms inside code blocks, links, or existing MDX components
598
614
  - Respects word boundaries to avoid partial matches
599
615
  - Handles plural forms (e.g., "API" matches "APIs")
616
+ - Resolves overlapping terms deterministically: when one term's range contains another's, the longer (superset) term wins; when two matches overlap but neither contains the other, the earlier match wins
600
617
 
601
618
  ## Troubleshooting
602
619
 
@@ -44,7 +44,8 @@ function remarkGlossaryTerms({
44
44
  terms = [],
45
45
  glossaryPath = null,
46
46
  routePath = "/glossary",
47
- siteDir = null
47
+ siteDir = null,
48
+ expandAcronymsOnFirstUse = false
48
49
  } = {}) {
49
50
  let glossaryTerms = terms;
50
51
  if (!glossaryTerms.length && glossaryPath && siteDir) {
@@ -133,7 +134,7 @@ function remarkGlossaryTerms({
133
134
  if (sortedTerms.length === 0) {
134
135
  return (tree) => tree;
135
136
  }
136
- function replaceTermsInText(text) {
137
+ function replaceTermsInText(text, seenTerms) {
137
138
  if (!text || !sortedTerms.length) {
138
139
  return [{ type: "text", value: text }];
139
140
  }
@@ -195,6 +196,8 @@ function remarkGlossaryTerms({
195
196
  value: text.substring(lastIndex, match.index)
196
197
  });
197
198
  }
199
+ const displayText = resolveDisplayText(match, text, seenTerms);
200
+ seenTerms.add(match.termObj.term);
198
201
  result.push({
199
202
  type: "mdxJsxFlowElement",
200
203
  name: "GlossaryTerm",
@@ -218,7 +221,7 @@ function remarkGlossaryTerms({
218
221
  children: [
219
222
  {
220
223
  type: "text",
221
- value: match.originalText
224
+ value: displayText
222
225
  }
223
226
  ]
224
227
  });
@@ -232,6 +235,20 @@ function remarkGlossaryTerms({
232
235
  }
233
236
  return result.length > 0 ? result : [{ type: "text", value: text }];
234
237
  }
238
+ function resolveDisplayText(match, text, seenTerms) {
239
+ const termObj = match.termObj;
240
+ if (!expandAcronymsOnFirstUse) return match.originalText;
241
+ if (!termObj.abbreviation) return match.originalText;
242
+ if (seenTerms.has(termObj.term)) return match.originalText;
243
+ const isCanonical = match.length === termObj.term.length && match.originalText.toLowerCase() === termObj.term.toLowerCase();
244
+ if (!isCanonical) return match.originalText;
245
+ const longForm = termObj.abbreviation;
246
+ const lookbackWindow = longForm.length + 10;
247
+ const lookbackStart = Math.max(0, match.index - lookbackWindow);
248
+ const lookback = text.substring(lookbackStart, match.index).toLowerCase();
249
+ if (lookback.includes(longForm.toLowerCase())) return match.originalText;
250
+ return `${longForm} (${match.originalText})`;
251
+ }
235
252
  function collectHeadingTextNodes(tree) {
236
253
  const skip = /* @__PURE__ */ new WeakSet();
237
254
  visit(tree, "heading", (headingNode) => {
@@ -244,6 +261,7 @@ function remarkGlossaryTerms({
244
261
  const transformer = (tree) => {
245
262
  let usedGlossaryTerm = false;
246
263
  const textNodesInHeadings = collectHeadingTextNodes(tree);
264
+ const seenTerms = /* @__PURE__ */ new Set();
247
265
  visit(tree, "text", (node, index, parent) => {
248
266
  if (parent.type === "code" || parent.type === "inlineCode" || parent.type === "link" || parent.type === "mdxJsxFlowElement" || parent.type === "mdxJsxTextElement") {
249
267
  return;
@@ -251,7 +269,7 @@ function remarkGlossaryTerms({
251
269
  if (textNodesInHeadings.has(node)) {
252
270
  return;
253
271
  }
254
- const replacements = replaceTermsInText(node.value);
272
+ const replacements = replaceTermsInText(node.value, seenTerms);
255
273
  if (replacements.length > 1 || replacements.length === 1 && replacements[0].type !== "text") {
256
274
  const newNodes = replacements.map((replacement) => {
257
275
  if (replacement.type === "mdxJsxFlowElement") {
@@ -331,4 +349,4 @@ export {
331
349
  remarkGlossaryTerms,
332
350
  clearGlossaryCache
333
351
  };
334
- //# sourceMappingURL=chunk-MG34SMDE.js.map
352
+ //# sourceMappingURL=chunk-CIAA7XR3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/remark/glossary-terms.js"],"sourcesContent":["import { visit } from 'unist-util-visit';\nimport path from 'path';\nimport fs from 'fs';\n\n/**\n * Simple validation for glossary terms loaded from file\n * Returns only valid terms with required fields\n *\n * @param {unknown} data - The parsed JSON data\n * @param {string} filePath - Path to the file (for error messages)\n * @returns {{ terms: Array<{term: string, definition: string}>, errors: string[] }}\n */\nfunction validateGlossaryTerms(data, _filePath) {\n const errors = [];\n const validTerms = [];\n\n if (data === null || data === undefined) {\n errors.push(`Glossary data is null or undefined`);\n return { terms: [], errors };\n }\n\n if (typeof data !== 'object') {\n errors.push(`Glossary data must be an object, got ${typeof data}`);\n return { terms: [], errors };\n }\n\n if (!('terms' in data)) {\n errors.push(`Glossary data must contain a \"terms\" array`);\n return { terms: [], errors };\n }\n\n if (!Array.isArray(data.terms)) {\n errors.push(`Field \"terms\" must be an array, got ${typeof data.terms}`);\n return { terms: [], errors };\n }\n\n data.terms.forEach((term, index) => {\n if (term === null || term === undefined || typeof term !== 'object') {\n errors.push(`terms[${index}]: Term must be an object`);\n return;\n }\n\n if (typeof term.term !== 'string' || term.term.trim() === '') {\n errors.push(`terms[${index}]: Missing or invalid \"term\" field`);\n return;\n }\n\n if (typeof term.definition !== 'string') {\n errors.push(`terms[${index}]: Missing or invalid \"definition\" field`);\n return;\n }\n\n validTerms.push(term);\n });\n\n return { terms: validTerms, errors };\n}\n\n// Cache for glossary data to avoid repeated synchronous file reads\n// Key: absolute file path, Value: { terms, loadedAt }\nconst glossaryCache = new Map();\nconst CACHE_TTL = 5000; // 5 seconds TTL to allow for file changes during dev\n\n/**\n * Creates a remark plugin that automatically detects and replaces glossary terms in markdown\n *\n * This plugin transforms plain text terms into <GlossaryTerm> JSX elements.\n * The GlossaryTerm component is globally available via the MDXComponents theme wrapper,\n * so no import injection is needed - MDX files can use it without explicit imports.\n *\n * @param {object} options - Plugin options\n * @param {Array} options.terms - Array of glossary term objects with {term, definition}\n * @param {string} options.glossaryPath - Path to glossary JSON file (optional, if terms not provided)\n * @param {string} options.routePath - Route path to glossary page (default: '/glossary')\n * @param {string} options.siteDir - Docusaurus site directory (required if using glossaryPath)\n * @param {boolean} options.expandAcronymsOnFirstUse - When true, the first canonical occurrence of a\n * term with an `abbreviation` is rendered as \"Long Form (Term)\" instead of just \"Term\".\n * Subsequent occurrences in the same file render unchanged. Default: false.\n * @returns {function} Remark plugin function\n */\nexport default function remarkGlossaryTerms({\n terms = [],\n glossaryPath = null,\n routePath = '/glossary',\n siteDir = null,\n expandAcronymsOnFirstUse = false,\n} = {}) {\n let glossaryTerms = terms;\n\n // If terms not provided, try to load from glossaryPath with caching\n if (!glossaryTerms.length && glossaryPath && siteDir) {\n try {\n const glossaryFilePath = path.resolve(siteDir, glossaryPath);\n const now = Date.now();\n\n // Check cache first to avoid repeated file reads\n const cached = glossaryCache.get(glossaryFilePath);\n if (cached && now - cached.loadedAt < CACHE_TTL) {\n glossaryTerms = cached.terms;\n } else {\n // Cache miss or expired - load from file synchronously\n // Note: This is synchronous I/O which can block the build process\n // Consider passing terms directly to avoid this\n if (fs.existsSync(glossaryFilePath)) {\n const fileContent = fs.readFileSync(glossaryFilePath, 'utf8');\n let glossaryData;\n try {\n glossaryData = JSON.parse(fileContent);\n } catch (parseError) {\n console.error(\n `[glossary-plugin] Failed to parse glossary JSON at ${glossaryPath}:`,\n parseError.message\n );\n glossaryCache.set(glossaryFilePath, {\n terms: [],\n loadedAt: now,\n });\n return tree => tree;\n }\n\n // Validate glossary data\n const { terms: validTerms, errors } = validateGlossaryTerms(glossaryData, glossaryPath);\n\n if (errors.length > 0) {\n console.warn(`[glossary-plugin] Glossary validation errors in ${glossaryPath}:`);\n errors.forEach(err => console.warn(` - ${err}`));\n if (validTerms.length > 0) {\n console.warn(`[glossary-plugin] Proceeding with ${validTerms.length} valid term(s).`);\n }\n }\n\n glossaryTerms = validTerms;\n\n // Update cache\n glossaryCache.set(glossaryFilePath, {\n terms: glossaryTerms,\n loadedAt: now,\n });\n\n // Log only once per file (when cache is first populated)\n if (!cached && process.env.NODE_ENV !== 'production') {\n console.log(\n `[glossary-plugin] Loaded ${glossaryTerms.length} terms from ${glossaryPath}`\n );\n }\n } else {\n // File doesn't exist - cache empty result to avoid repeated checks\n glossaryCache.set(glossaryFilePath, {\n terms: [],\n loadedAt: now,\n });\n if (process.env.NODE_ENV !== 'production') {\n console.warn(`[glossary-plugin] Glossary file not found: ${glossaryPath}`);\n }\n }\n }\n } catch (error) {\n console.warn(\n `[glossary-plugin] Failed to load glossary from ${glossaryPath}:`,\n error.message\n );\n // Cache the error to avoid repeated attempts\n if (glossaryPath && siteDir) {\n const glossaryFilePath = path.resolve(siteDir, glossaryPath);\n glossaryCache.set(glossaryFilePath, {\n terms: [],\n loadedAt: Date.now(),\n });\n }\n }\n }\n\n // Build a map of terms for efficient lookup, skipping terms with autoLink: false.\n // Each entry represents a matchable phrase (canonical term or alias) and points\n // back to the canonical term object so tooltip/href always use the canonical form.\n // Key: lowercase phrase, Value: { termObj, phrase, caseSensitive } where\n // `phrase` preserves the original case (used for case-sensitive matching).\n const termMap = new Map();\n glossaryTerms.forEach(termObj => {\n if (!termObj.term || termObj.autoLink === false) return;\n const caseSensitive = termObj.caseSensitive === true;\n\n const register = phrase => {\n if (typeof phrase !== 'string' || phrase.trim() === '') return;\n const key = phrase.toLowerCase();\n if (!termMap.has(key)) {\n termMap.set(key, { termObj, phrase, caseSensitive });\n }\n };\n\n register(termObj.term);\n if (Array.isArray(termObj.aliases)) {\n termObj.aliases.forEach(register);\n }\n });\n\n // Sort terms by length (longest first) to avoid partial matches\n // e.g., \"Application Programming Interface\" should match before \"API\"\n const sortedTerms = Array.from(termMap.entries()).sort((a, b) => b[0].length - a[0].length);\n\n // If no terms, return a no-op transformer\n if (sortedTerms.length === 0) {\n return tree => tree;\n }\n\n /**\n * Recursively replace glossary terms in text\n * Returns an array of text nodes and MDX components\n *\n * @param {string} text - Source text to scan\n * @param {Set<string>} seenTerms - Per-file set tracking which canonical terms have already been\n * rendered (used by expandAcronymsOnFirstUse to expand only on first occurrence).\n */\n function replaceTermsInText(text, seenTerms) {\n if (!text || !sortedTerms.length) {\n return [{ type: 'text', value: text }];\n }\n\n const result = [];\n let lastIndex = 0;\n const textLower = text.toLowerCase();\n\n // Find all matches\n const matches = [];\n for (const [lowerPhrase, { termObj, phrase, caseSensitive }] of sortedTerms) {\n // Case-sensitive terms search the original text for the exact casing;\n // case-insensitive terms search the lowercased text for the lowercased phrase.\n const haystack = caseSensitive ? text : textLower;\n const needle = caseSensitive ? phrase : lowerPhrase;\n let searchIndex = 0;\n\n while (searchIndex < haystack.length) {\n const index = haystack.indexOf(needle, searchIndex);\n if (index === -1) break;\n\n // Check if it's a whole word match, with simple plural tolerance ('s' or 'es').\n // Word-boundary detection uses the lowercased text so letter-class checks\n // behave consistently regardless of the term's case-sensitivity setting.\n const beforeChar = index > 0 ? textLower[index - 1] : ' ';\n const afterIndex = index + needle.length;\n const afterChar = afterIndex < textLower.length ? textLower[afterIndex] : ' ';\n\n let matchLength = needle.length;\n let isWordBoundary = !/\\w/.test(beforeChar) && !/\\w/.test(afterChar);\n\n // Allow trailing 's' plural (e.g., webhook -> webhooks)\n if (!isWordBoundary && afterChar === 's') {\n const nextChar = afterIndex + 1 < textLower.length ? textLower[afterIndex + 1] : ' ';\n if (!/\\w/.test(nextChar)) {\n isWordBoundary = true;\n matchLength = needle.length + 1;\n }\n }\n\n // Allow trailing 'es' plural (e.g., API -> APIs, box -> boxes)\n if (\n !isWordBoundary &&\n afterChar === 'e' &&\n afterIndex + 1 < textLower.length &&\n textLower[afterIndex + 1] === 's'\n ) {\n const nextChar = afterIndex + 2 < textLower.length ? textLower[afterIndex + 2] : ' ';\n if (!/\\w/.test(nextChar)) {\n isWordBoundary = true;\n matchLength = needle.length + 2;\n }\n }\n\n if (isWordBoundary) {\n matches.push({\n index,\n length: matchLength,\n termObj: termObj,\n // Store original case from the text (what the reader actually wrote)\n originalText: text.substring(index, index + matchLength),\n });\n }\n\n searchIndex = index + 1;\n }\n }\n\n // Sort matches by index\n matches.sort((a, b) => a.index - b.index);\n\n // Remove overlapping matches (keep the first one)\n const nonOverlappingMatches = [];\n let lastMatchEnd = 0;\n for (const match of matches) {\n if (match.index >= lastMatchEnd) {\n nonOverlappingMatches.push(match);\n lastMatchEnd = match.index + match.length;\n }\n }\n\n // Build result array\n for (const match of nonOverlappingMatches) {\n // Add text before match\n if (match.index > lastIndex) {\n result.push({\n type: 'text',\n value: text.substring(lastIndex, match.index),\n });\n }\n\n const displayText = resolveDisplayText(match, text, seenTerms);\n // Mark this term as seen regardless of whether we expanded — once the reader\n // has encountered any occurrence (canonical or otherwise), the introduction\n // window has closed.\n seenTerms.add(match.termObj.term);\n\n // Add MDX component for glossary term\n result.push({\n type: 'mdxJsxFlowElement',\n name: 'GlossaryTerm',\n attributes: [\n {\n type: 'mdxJsxAttribute',\n name: 'term',\n value: match.termObj.term,\n },\n {\n type: 'mdxJsxAttribute',\n name: 'definition',\n value: match.termObj.definition || '',\n },\n {\n type: 'mdxJsxAttribute',\n name: 'routePath',\n value: routePath,\n },\n ],\n children: [\n {\n type: 'text',\n value: displayText,\n },\n ],\n });\n\n lastIndex = match.index + match.length;\n }\n\n // Add remaining text\n if (lastIndex < text.length) {\n result.push({\n type: 'text',\n value: text.substring(lastIndex),\n });\n }\n\n return result.length > 0 ? result : [{ type: 'text', value: text }];\n }\n\n // Decide what text to render inside the GlossaryTerm element.\n // Default: the text as written. When expandAcronymsOnFirstUse is enabled and this is the\n // first canonical occurrence of a term that has an `abbreviation`, expand to\n // \"Long Form (Term)\" — unless the long form already appears immediately before the match\n // (e.g. the author wrote \"Payment Service Provider (PSP)\" themselves).\n function resolveDisplayText(match, text, seenTerms) {\n const termObj = match.termObj;\n if (!expandAcronymsOnFirstUse) return match.originalText;\n if (!termObj.abbreviation) return match.originalText;\n if (seenTerms.has(termObj.term)) return match.originalText;\n\n // Only canonical matches — no aliases, no plural forms.\n const isCanonical =\n match.length === termObj.term.length &&\n match.originalText.toLowerCase() === termObj.term.toLowerCase();\n if (!isCanonical) return match.originalText;\n\n // If the long form is already present in the lookback window, skip expansion to\n // avoid \"Payment Service Provider (Payment Service Provider (PSP))\".\n const longForm = termObj.abbreviation;\n const lookbackWindow = longForm.length + 10;\n const lookbackStart = Math.max(0, match.index - lookbackWindow);\n const lookback = text.substring(lookbackStart, match.index).toLowerCase();\n if (lookback.includes(longForm.toLowerCase())) return match.originalText;\n\n return `${longForm} (${match.originalText})`;\n }\n\n // Collect text nodes that live inside a heading (h1-h6) so we can skip them.\n // Headings are excluded from auto-linking because glossary anchors inside\n // headings clash with the heading's own link/anchor behavior and are noisy.\n function collectHeadingTextNodes(tree) {\n const skip = new WeakSet();\n visit(tree, 'heading', headingNode => {\n visit(headingNode, 'text', textNode => {\n skip.add(textNode);\n });\n });\n return skip;\n }\n\n // Return the transformer function\n const transformer = tree => {\n let usedGlossaryTerm = false;\n const textNodesInHeadings = collectHeadingTextNodes(tree);\n // Per-file tracking: each transformer invocation gets a fresh Set so acronym\n // expansion fires at most once per term per file.\n const seenTerms = new Set();\n visit(tree, 'text', (node, index, parent) => {\n // Skip text nodes inside code blocks, links, or existing MDX components\n if (\n parent.type === 'code' ||\n parent.type === 'inlineCode' ||\n parent.type === 'link' ||\n parent.type === 'mdxJsxFlowElement' ||\n parent.type === 'mdxJsxTextElement'\n ) {\n return;\n }\n\n // Skip text nodes that are descendants of a heading (h1-h6)\n if (textNodesInHeadings.has(node)) {\n return;\n }\n\n // Replace terms in text node\n const replacements = replaceTermsInText(node.value, seenTerms);\n\n // If we have replacements, replace the single text node with multiple nodes\n if (\n replacements.length > 1 ||\n (replacements.length === 1 && replacements[0].type !== 'text')\n ) {\n // Convert to text elements for paragraph context if needed\n const newNodes = replacements.map(replacement => {\n if (replacement.type === 'mdxJsxFlowElement') {\n // In paragraph context, we need mdxJsxTextElement instead\n if (parent.type === 'paragraph') {\n usedGlossaryTerm = true;\n return {\n type: 'mdxJsxTextElement',\n name: replacement.name,\n attributes: replacement.attributes,\n children: replacement.children,\n };\n }\n usedGlossaryTerm = true;\n }\n return replacement;\n });\n\n // Replace the single node with multiple nodes\n parent.children.splice(index, 1, ...newNodes);\n return index + newNodes.length - 1; // Return new index to continue\n }\n });\n\n // Inject MDX import for GlossaryTerm if we used it\n // The component is available via theme path, so we just need to import it\n if (usedGlossaryTerm) {\n const importNode = {\n type: 'mdxjsEsm',\n value: 'import GlossaryTerm from \"@theme/GlossaryTerm\";',\n data: {\n estree: {\n type: 'Program',\n sourceType: 'module',\n body: [\n {\n type: 'ImportDeclaration',\n specifiers: [\n {\n type: 'ImportDefaultSpecifier',\n local: { type: 'Identifier', name: 'GlossaryTerm' },\n },\n ],\n source: {\n type: 'Literal',\n value: '@theme/GlossaryTerm',\n raw: '\"@theme/GlossaryTerm\"',\n },\n },\n ],\n },\n },\n };\n\n // Check for existing import\n const hasImport =\n Array.isArray(tree.children) &&\n tree.children.some(\n n =>\n n.type === 'mdxjsEsm' &&\n (n.value?.includes('@theme/GlossaryTerm') ||\n n.data?.estree?.body?.some(s => s.source?.value === '@theme/GlossaryTerm'))\n );\n\n if (!hasImport) {\n if (!Array.isArray(tree.children)) tree.children = [];\n let insertIndex = 0;\n for (let i = 0; i < tree.children.length; i++) {\n const node = tree.children[i];\n if (node.type === 'yaml' || node.type === 'toml') {\n insertIndex = i + 1;\n } else {\n break;\n }\n }\n tree.children.splice(insertIndex, 0, importNode);\n }\n }\n };\n\n return transformer;\n}\n\n/**\n * Clears the glossary cache\n * Useful for testing or when you want to force a reload of glossary data\n *\n * @param {string} [filePath] - Optional specific file path to clear. If not provided, clears entire cache.\n */\nexport function clearGlossaryCache(filePath) {\n if (filePath) {\n glossaryCache.delete(filePath);\n } else {\n glossaryCache.clear();\n }\n}\n"],"mappings":"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","names":[]}
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-FJKK57G2.js";
5
5
  import {
6
6
  remarkGlossaryTerms
7
- } from "./chunk-MG34SMDE.js";
7
+ } from "./chunk-CIAA7XR3.js";
8
8
 
9
9
  // src/index.ts
10
10
  import path from "path";
@@ -89,14 +89,19 @@ function glossaryPlugin(context, options = {}) {
89
89
  }
90
90
  var remarkPlugin = remarkGlossaryTerms;
91
91
  function getRemarkPlugin(pluginOptions, context) {
92
- const { glossaryPath = "glossary/glossary.json", routePath = "/glossary" } = pluginOptions;
92
+ const {
93
+ glossaryPath = "glossary/glossary.json",
94
+ routePath = "/glossary",
95
+ expandAcronymsOnFirstUse = false
96
+ } = pluginOptions;
93
97
  const siteDir = context?.siteDir;
94
98
  return [
95
99
  remarkGlossaryTerms,
96
100
  {
97
101
  glossaryPath,
98
102
  routePath,
99
- siteDir
103
+ siteDir,
104
+ expandAcronymsOnFirstUse
100
105
  }
101
106
  ];
102
107
  }
@@ -106,4 +111,4 @@ export {
106
111
  remarkPlugin,
107
112
  getRemarkPlugin
108
113
  };
109
- //# sourceMappingURL=chunk-33WWYCNA.js.map
114
+ //# sourceMappingURL=chunk-EQBYCLUI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @ts-ignore\nimport path from 'path';\n// @ts-ignore\nimport { fileURLToPath } from 'url';\nimport fs from 'fs-extra';\nimport type { LoadContext, Plugin } from '@docusaurus/types';\nimport validatePeerDependencies from 'validate-peer-dependencies';\nimport remarkGlossaryTerms from './remark/glossary-terms.js';\nimport { validateGlossaryData, GlossaryValidationError } from './validation.js';\nimport type { GlossaryPluginOptions, GlossaryData } from './types.js';\n\n// Re-export the shared public types so existing\n// `import type { GlossaryTerm } from 'docusaurus-plugin-glossary'` imports keep working.\nexport type { GlossaryPluginOptions, GlossaryTerm, GlossaryData } from './types.js';\n\n// Standard ES module directory resolution\nconst currentFilePath = fileURLToPath(import.meta.url);\nconst currentDir = path.dirname(currentFilePath);\n\n// Validate peer dependencies at module load time\nvalidatePeerDependencies(currentDir);\n\n/**\n * Docusaurus Glossary Plugin\n *\n * A plugin that provides glossary functionality with:\n * - Glossary terms defined in a JSON file\n * - Auto-generated glossary page with term definitions\n * - GlossaryTerm component for inline definitions with interactive tooltips\n * - Automatic client-side initialization via getClientModules() (no manual imports needed)\n * - Optional automatic glossary term detection in markdown files via remark plugin\n *\n * ## Basic Usage (Manual Term Markup)\n *\n * Just install the plugin - the GlossaryTerm component is automatically available:\n * ```javascript\n * module.exports = {\n * plugins: [\n * ['docusaurus-plugin-glossary', {\n * glossaryPath: 'glossary/glossary.json',\n * routePath: '/glossary',\n * }],\n * ],\n * };\n * ```\n *\n * Then use `<GlossaryTerm>` in your MDX files without importing:\n * ```mdx\n * <GlossaryTerm term=\"API\">API</GlossaryTerm>\n * ```\n *\n * ## Advanced Usage (Automatic Term Detection)\n *\n * To automatically detect and link glossary terms in markdown, add the remark plugin:\n * ```javascript\n * const glossaryPlugin = require('docusaurus-plugin-glossary');\n *\n * module.exports = {\n * presets: [\n * ['@docusaurus/preset-classic', {\n * docs: {\n * remarkPlugins: [\n * glossaryPlugin.getRemarkPlugin({\n * glossaryPath: 'glossary/glossary.json',\n * routePath: '/glossary',\n * }, { siteDir: __dirname }),\n * ],\n * },\n * }],\n * ],\n * plugins: [\n * ['docusaurus-plugin-glossary', {\n * glossaryPath: 'glossary/glossary.json',\n * routePath: '/glossary',\n * }],\n * ],\n * };\n * ```\n *\n * @param context - Docusaurus context\n * @param options - Plugin options\n * @param options.glossaryPath - Path to glossary JSON file (default: 'glossary/glossary.json')\n * @param options.routePath - Route path for glossary page (default: '/glossary')\n * @param options.autoLinkTerms - Legacy option, kept for compatibility but no longer used (configure remark plugin manually instead)\n * @returns Plugin object\n */\nexport default function glossaryPlugin(\n context: LoadContext,\n options: GlossaryPluginOptions = {}\n): Plugin {\n const { glossaryPath = 'glossary/glossary.json', routePath = '/glossary' } = options;\n\n return {\n name: 'docusaurus-plugin-glossary',\n\n getClientModules() {\n return [path.resolve(currentDir, './client/index.js')];\n },\n\n async loadContent() {\n // Load glossary terms from JSON file\n const glossaryFilePath = path.resolve(context.siteDir, glossaryPath);\n\n if (await fs.pathExists(glossaryFilePath)) {\n try {\n const rawData = await fs.readJson(glossaryFilePath);\n\n // Validate glossary data structure\n const validationResult = validateGlossaryData(rawData, { throwOnError: false });\n\n if (!validationResult.valid) {\n console.warn(\n `[glossary-plugin] Glossary file has validation errors at ${glossaryFilePath}:`\n );\n validationResult.errors.forEach(err => {\n console.warn(` - [${err.field}] ${err.message}`);\n });\n console.warn('[glossary-plugin] Proceeding with valid terms only.');\n }\n\n return validationResult.data;\n } catch (error) {\n if (error instanceof GlossaryValidationError) {\n throw error;\n }\n // JSON parsing error\n throw new Error(\n `Failed to parse glossary file at ${glossaryFilePath}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n console.warn(`Glossary file not found at ${glossaryFilePath}. Using empty glossary.`);\n return { terms: [] };\n },\n\n async contentLoaded({ content, actions }) {\n const { createData, addRoute, setGlobalData } = actions;\n const glossaryContent = content as GlossaryData;\n\n // Create data file that can be imported by components\n const glossaryDataPath = await createData(\n 'glossary-data.json',\n JSON.stringify(glossaryContent)\n );\n\n // Create a data file for the remark plugin to access glossary terms\n await createData(\n 'remark-glossary-data.json',\n JSON.stringify({\n terms: glossaryContent.terms || [],\n routePath: routePath,\n })\n );\n\n // Add glossary page route\n addRoute({\n path: routePath,\n component: path.join(currentDir, 'components/GlossaryPage.js'),\n exact: true,\n modules: {\n glossaryData: glossaryDataPath,\n },\n });\n\n // Expose global data for runtime lookups (used by GlossaryTerm)\n setGlobalData({\n terms: glossaryContent.terms || [],\n routePath,\n });\n },\n\n getThemePath() {\n return path.resolve(currentDir, './theme');\n },\n\n getPathsToWatch() {\n return [path.resolve(context.siteDir, glossaryPath)];\n },\n\n async postBuild() {\n // You can add any post-build steps here if needed\n console.log('Glossary plugin: Build completed');\n },\n };\n}\n\n// Export remark plugin factory for use in markdown configuration\nexport const remarkPlugin = remarkGlossaryTerms;\n\n// Export cache clearing utility\nexport { clearGlossaryCache } from './remark/glossary-terms.js';\n\n// Export validation utilities\nexport {\n validateGlossaryData,\n GlossaryValidationError,\n formatValidationErrors,\n type ValidationError,\n type ValidationResult,\n} from './validation.js';\n\n/**\n * Helper function to get the configured remark plugin\n * This can be used in docusaurus.config.js markdown configuration\n *\n * @param pluginOptions - Plugin options from docusaurus.config.js\n * @param context - Context with siteDir\n * @returns Configured remark plugin\n */\nexport function getRemarkPlugin(\n pluginOptions: GlossaryPluginOptions,\n context?: { siteDir?: string }\n): [\n typeof remarkGlossaryTerms,\n {\n glossaryPath: string;\n routePath: string;\n siteDir?: string;\n expandAcronymsOnFirstUse: boolean;\n },\n] {\n const {\n glossaryPath = 'glossary/glossary.json',\n routePath = '/glossary',\n expandAcronymsOnFirstUse = false,\n } = pluginOptions;\n\n const siteDir = context?.siteDir;\n\n return [\n remarkGlossaryTerms,\n {\n glossaryPath,\n routePath,\n siteDir,\n expandAcronymsOnFirstUse,\n },\n ];\n}\n"],"mappings":";;;;;;;;;AACA,OAAO,UAAU;AAEjB,SAAS,qBAAqB;AAC9B,OAAO,QAAQ;AAEf,OAAO,8BAA8B;AAUrC,IAAM,kBAAkB,cAAc,YAAY,GAAG;AACrD,IAAM,aAAa,KAAK,QAAQ,eAAe;AAG/C,yBAAyB,UAAU;AAkEpB,SAAR,eACL,SACA,UAAiC,CAAC,GAC1B;AACR,QAAM,EAAE,eAAe,0BAA0B,YAAY,YAAY,IAAI;AAE7E,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,mBAAmB;AACjB,aAAO,CAAC,KAAK,QAAQ,YAAY,mBAAmB,CAAC;AAAA,IACvD;AAAA,IAEA,MAAM,cAAc;AAElB,YAAM,mBAAmB,KAAK,QAAQ,QAAQ,SAAS,YAAY;AAEnE,UAAI,MAAM,GAAG,WAAW,gBAAgB,GAAG;AACzC,YAAI;AACF,gBAAM,UAAU,MAAM,GAAG,SAAS,gBAAgB;AAGlD,gBAAM,mBAAmB,qBAAqB,SAAS,EAAE,cAAc,MAAM,CAAC;AAE9E,cAAI,CAAC,iBAAiB,OAAO;AAC3B,oBAAQ;AAAA,cACN,4DAA4D,gBAAgB;AAAA,YAC9E;AACA,6BAAiB,OAAO,QAAQ,SAAO;AACrC,sBAAQ,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,OAAO,EAAE;AAAA,YAClD,CAAC;AACD,oBAAQ,KAAK,qDAAqD;AAAA,UACpE;AAEA,iBAAO,iBAAiB;AAAA,QAC1B,SAAS,OAAO;AACd,cAAI,iBAAiB,yBAAyB;AAC5C,kBAAM;AAAA,UACR;AAEA,gBAAM,IAAI;AAAA,YACR,oCAAoC,gBAAgB,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,UACjH;AAAA,QACF;AAAA,MACF;AAEA,cAAQ,KAAK,8BAA8B,gBAAgB,yBAAyB;AACpF,aAAO,EAAE,OAAO,CAAC,EAAE;AAAA,IACrB;AAAA,IAEA,MAAM,cAAc,EAAE,SAAS,QAAQ,GAAG;AACxC,YAAM,EAAE,YAAY,UAAU,cAAc,IAAI;AAChD,YAAM,kBAAkB;AAGxB,YAAM,mBAAmB,MAAM;AAAA,QAC7B;AAAA,QACA,KAAK,UAAU,eAAe;AAAA,MAChC;AAGA,YAAM;AAAA,QACJ;AAAA,QACA,KAAK,UAAU;AAAA,UACb,OAAO,gBAAgB,SAAS,CAAC;AAAA,UACjC;AAAA,QACF,CAAC;AAAA,MACH;AAGA,eAAS;AAAA,QACP,MAAM;AAAA,QACN,WAAW,KAAK,KAAK,YAAY,4BAA4B;AAAA,QAC7D,OAAO;AAAA,QACP,SAAS;AAAA,UACP,cAAc;AAAA,QAChB;AAAA,MACF,CAAC;AAGD,oBAAc;AAAA,QACZ,OAAO,gBAAgB,SAAS,CAAC;AAAA,QACjC;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,eAAe;AACb,aAAO,KAAK,QAAQ,YAAY,SAAS;AAAA,IAC3C;AAAA,IAEA,kBAAkB;AAChB,aAAO,CAAC,KAAK,QAAQ,QAAQ,SAAS,YAAY,CAAC;AAAA,IACrD;AAAA,IAEA,MAAM,YAAY;AAEhB,cAAQ,IAAI,kCAAkC;AAAA,IAChD;AAAA,EACF;AACF;AAGO,IAAM,eAAe;AAsBrB,SAAS,gBACd,eACA,SASA;AACA,QAAM;AAAA,IACJ,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,2BAA2B;AAAA,EAC7B,IAAI;AAEJ,QAAM,UAAU,SAAS;AAEzB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -59,9 +59,10 @@ function GlossaryPage({ glossaryData }) {
59
59
  const filteredTerms = (0, import_react.useMemo)(() => {
60
60
  if (!searchTerm) return terms;
61
61
  const lowerSearch = searchTerm.toLowerCase();
62
- return terms.filter(
63
- (term) => term.term.toLowerCase().includes(lowerSearch) || term.definition.toLowerCase().includes(lowerSearch)
64
- );
62
+ return terms.filter((term) => {
63
+ const haystack = [term.term, term.definition, term.abbreviation, ...term.aliases || []].filter(Boolean).join(" ").toLowerCase();
64
+ return haystack.includes(lowerSearch);
65
+ });
65
66
  }, [terms, searchTerm]);
66
67
  const groupedTerms = (0, import_react.useMemo)(() => {
67
68
  return groupTermsByLetter(filteredTerms);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/GlossaryPage.js"],"sourcesContent":["import React, { useState, useMemo } from 'react';\nimport Layout from '@theme/Layout';\nimport useDocusaurusContext from '@docusaurus/useDocusaurusContext';\nimport styles from './GlossaryPage.module.css';\n\n/**\n * Groups glossary terms by their first letter\n */\nfunction groupTermsByLetter(terms) {\n const grouped = {};\n\n terms.forEach(term => {\n const firstLetter = term.term.charAt(0).toUpperCase();\n if (!grouped[firstLetter]) {\n grouped[firstLetter] = [];\n }\n grouped[firstLetter].push(term);\n });\n\n // Sort each group alphabetically\n Object.keys(grouped).forEach(letter => {\n grouped[letter].sort((a, b) => a.term.localeCompare(b.term));\n });\n\n return grouped;\n}\n\n/**\n * GlossaryPage component - displays all glossary terms\n */\nexport default function GlossaryPage({ glossaryData }) {\n useDocusaurusContext();\n const [searchTerm, setSearchTerm] = useState('');\n\n const terms = useMemo(() => glossaryData?.terms || [], [glossaryData?.terms]);\n\n // Filter terms based on search\n const filteredTerms = useMemo(() => {\n if (!searchTerm) return terms;\n\n const lowerSearch = searchTerm.toLowerCase();\n return terms.filter(\n term =>\n term.term.toLowerCase().includes(lowerSearch) ||\n term.definition.toLowerCase().includes(lowerSearch)\n );\n }, [terms, searchTerm]);\n\n // Group terms by first letter\n const groupedTerms = useMemo(() => {\n return groupTermsByLetter(filteredTerms);\n }, [filteredTerms]);\n\n const letters = Object.keys(groupedTerms).sort();\n\n const glossaryTitle = glossaryData?.title || 'Glossary';\n\n return (\n <Layout title={glossaryTitle} description=\"A glossary of terms and definitions\">\n <div className={styles.glossaryContainer}>\n <header className={styles.glossaryHeader}>\n <h1>{glossaryTitle}</h1>\n <p className={styles.glossaryDescription}>\n {glossaryData?.description || 'A collection of terms and their definitions'}\n </p>\n\n <div className={styles.searchContainer}>\n <input\n type=\"text\"\n placeholder=\"Search terms...\"\n className={styles.searchInput}\n value={searchTerm}\n onChange={e => setSearchTerm(e.target.value)}\n />\n </div>\n </header>\n\n {filteredTerms.length === 0 ? (\n <div className={styles.noResults}>\n <p>No terms found matching \"{searchTerm}\"</p>\n </div>\n ) : (\n <div className={styles.glossaryContent}>\n {/* Letter navigation */}\n <nav className={styles.letterNav}>\n {letters.map(letter => (\n <a key={letter} href={`#letter-${letter}`} className={styles.letterLink}>\n {letter}\n </a>\n ))}\n </nav>\n\n {/* Terms grouped by letter */}\n {letters.map(letter => (\n <section key={letter} id={`letter-${letter}`} className={styles.letterSection}>\n <h2 className={styles.letterHeading}>{letter}</h2>\n <dl className={styles.termList}>\n {groupedTerms[letter].map((term, index) => (\n <div\n key={`${letter}-${index}`}\n className={styles.termItem}\n id={term.id || term.term.toLowerCase().replace(/\\s+/g, '-')}\n >\n <dt className={styles.termName}>\n {term.term}\n {term.abbreviation && (\n <span className={styles.abbreviation}> ({term.abbreviation})</span>\n )}\n </dt>\n <dd className={styles.termDefinition}>\n {term.definition}\n {term.relatedTerms && term.relatedTerms.length > 0 && (\n <div className={styles.relatedTerms}>\n <strong>Related terms:</strong>{' '}\n {term.relatedTerms.map((related, idx) => (\n <React.Fragment key={idx}>\n {idx > 0 && ', '}\n <a href={`#${related.toLowerCase().replace(/\\s+/g, '-')}`}>\n {related}\n </a>\n </React.Fragment>\n ))}\n </div>\n )}\n </dd>\n </div>\n ))}\n </dl>\n </section>\n ))}\n </div>\n )}\n\n <footer className={styles.glossaryFooter}>\n <p>Total terms: {terms.length}</p>\n </footer>\n </div>\n </Layout>\n );\n}\n"],"mappings":"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","names":["useDocusaurusContext","Layout","styles","React"]}
1
+ {"version":3,"sources":["../../src/components/GlossaryPage.js"],"sourcesContent":["import React, { useState, useMemo } from 'react';\nimport Layout from '@theme/Layout';\nimport useDocusaurusContext from '@docusaurus/useDocusaurusContext';\nimport styles from './GlossaryPage.module.css';\n\n/**\n * Groups glossary terms by their first letter\n */\nfunction groupTermsByLetter(terms) {\n const grouped = {};\n\n terms.forEach(term => {\n const firstLetter = term.term.charAt(0).toUpperCase();\n if (!grouped[firstLetter]) {\n grouped[firstLetter] = [];\n }\n grouped[firstLetter].push(term);\n });\n\n // Sort each group alphabetically\n Object.keys(grouped).forEach(letter => {\n grouped[letter].sort((a, b) => a.term.localeCompare(b.term));\n });\n\n return grouped;\n}\n\n/**\n * GlossaryPage component - displays all glossary terms\n */\nexport default function GlossaryPage({ glossaryData }) {\n useDocusaurusContext();\n const [searchTerm, setSearchTerm] = useState('');\n\n const terms = useMemo(() => glossaryData?.terms || [], [glossaryData?.terms]);\n\n // Filter terms based on search\n const filteredTerms = useMemo(() => {\n if (!searchTerm) return terms;\n\n const lowerSearch = searchTerm.toLowerCase();\n return terms.filter(term => {\n const haystack = [term.term, term.definition, term.abbreviation, ...(term.aliases || [])]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n return haystack.includes(lowerSearch);\n });\n }, [terms, searchTerm]);\n\n // Group terms by first letter\n const groupedTerms = useMemo(() => {\n return groupTermsByLetter(filteredTerms);\n }, [filteredTerms]);\n\n const letters = Object.keys(groupedTerms).sort();\n\n const glossaryTitle = glossaryData?.title || 'Glossary';\n\n return (\n <Layout title={glossaryTitle} description=\"A glossary of terms and definitions\">\n <div className={styles.glossaryContainer}>\n <header className={styles.glossaryHeader}>\n <h1>{glossaryTitle}</h1>\n <p className={styles.glossaryDescription}>\n {glossaryData?.description || 'A collection of terms and their definitions'}\n </p>\n\n <div className={styles.searchContainer}>\n <input\n type=\"text\"\n placeholder=\"Search terms...\"\n className={styles.searchInput}\n value={searchTerm}\n onChange={e => setSearchTerm(e.target.value)}\n />\n </div>\n </header>\n\n {filteredTerms.length === 0 ? (\n <div className={styles.noResults}>\n <p>No terms found matching \"{searchTerm}\"</p>\n </div>\n ) : (\n <div className={styles.glossaryContent}>\n {/* Letter navigation */}\n <nav className={styles.letterNav}>\n {letters.map(letter => (\n <a key={letter} href={`#letter-${letter}`} className={styles.letterLink}>\n {letter}\n </a>\n ))}\n </nav>\n\n {/* Terms grouped by letter */}\n {letters.map(letter => (\n <section key={letter} id={`letter-${letter}`} className={styles.letterSection}>\n <h2 className={styles.letterHeading}>{letter}</h2>\n <dl className={styles.termList}>\n {groupedTerms[letter].map((term, index) => (\n <div\n key={`${letter}-${index}`}\n className={styles.termItem}\n id={term.id || term.term.toLowerCase().replace(/\\s+/g, '-')}\n >\n <dt className={styles.termName}>\n {term.term}\n {term.abbreviation && (\n <span className={styles.abbreviation}> ({term.abbreviation})</span>\n )}\n </dt>\n <dd className={styles.termDefinition}>\n {term.definition}\n {term.relatedTerms && term.relatedTerms.length > 0 && (\n <div className={styles.relatedTerms}>\n <strong>Related terms:</strong>{' '}\n {term.relatedTerms.map((related, idx) => (\n <React.Fragment key={idx}>\n {idx > 0 && ', '}\n <a href={`#${related.toLowerCase().replace(/\\s+/g, '-')}`}>\n {related}\n </a>\n </React.Fragment>\n ))}\n </div>\n )}\n </dd>\n </div>\n ))}\n </dl>\n </section>\n ))}\n </div>\n )}\n\n <footer className={styles.glossaryFooter}>\n <p>Total terms: {terms.length}</p>\n </footer>\n </div>\n </Layout>\n );\n}\n"],"mappings":"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","names":["useDocusaurusContext","Layout","styles","React"]}
@@ -25,9 +25,10 @@ function GlossaryPage({ glossaryData }) {
25
25
  const filteredTerms = useMemo(() => {
26
26
  if (!searchTerm) return terms;
27
27
  const lowerSearch = searchTerm.toLowerCase();
28
- return terms.filter(
29
- (term) => term.term.toLowerCase().includes(lowerSearch) || term.definition.toLowerCase().includes(lowerSearch)
30
- );
28
+ return terms.filter((term) => {
29
+ const haystack = [term.term, term.definition, term.abbreviation, ...term.aliases || []].filter(Boolean).join(" ").toLowerCase();
30
+ return haystack.includes(lowerSearch);
31
+ });
31
32
  }, [terms, searchTerm]);
32
33
  const groupedTerms = useMemo(() => {
33
34
  return groupTermsByLetter(filteredTerms);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/GlossaryPage.js"],"sourcesContent":["import React, { useState, useMemo } from 'react';\nimport Layout from '@theme/Layout';\nimport useDocusaurusContext from '@docusaurus/useDocusaurusContext';\nimport styles from './GlossaryPage.module.css';\n\n/**\n * Groups glossary terms by their first letter\n */\nfunction groupTermsByLetter(terms) {\n const grouped = {};\n\n terms.forEach(term => {\n const firstLetter = term.term.charAt(0).toUpperCase();\n if (!grouped[firstLetter]) {\n grouped[firstLetter] = [];\n }\n grouped[firstLetter].push(term);\n });\n\n // Sort each group alphabetically\n Object.keys(grouped).forEach(letter => {\n grouped[letter].sort((a, b) => a.term.localeCompare(b.term));\n });\n\n return grouped;\n}\n\n/**\n * GlossaryPage component - displays all glossary terms\n */\nexport default function GlossaryPage({ glossaryData }) {\n useDocusaurusContext();\n const [searchTerm, setSearchTerm] = useState('');\n\n const terms = useMemo(() => glossaryData?.terms || [], [glossaryData?.terms]);\n\n // Filter terms based on search\n const filteredTerms = useMemo(() => {\n if (!searchTerm) return terms;\n\n const lowerSearch = searchTerm.toLowerCase();\n return terms.filter(\n term =>\n term.term.toLowerCase().includes(lowerSearch) ||\n term.definition.toLowerCase().includes(lowerSearch)\n );\n }, [terms, searchTerm]);\n\n // Group terms by first letter\n const groupedTerms = useMemo(() => {\n return groupTermsByLetter(filteredTerms);\n }, [filteredTerms]);\n\n const letters = Object.keys(groupedTerms).sort();\n\n const glossaryTitle = glossaryData?.title || 'Glossary';\n\n return (\n <Layout title={glossaryTitle} description=\"A glossary of terms and definitions\">\n <div className={styles.glossaryContainer}>\n <header className={styles.glossaryHeader}>\n <h1>{glossaryTitle}</h1>\n <p className={styles.glossaryDescription}>\n {glossaryData?.description || 'A collection of terms and their definitions'}\n </p>\n\n <div className={styles.searchContainer}>\n <input\n type=\"text\"\n placeholder=\"Search terms...\"\n className={styles.searchInput}\n value={searchTerm}\n onChange={e => setSearchTerm(e.target.value)}\n />\n </div>\n </header>\n\n {filteredTerms.length === 0 ? (\n <div className={styles.noResults}>\n <p>No terms found matching \"{searchTerm}\"</p>\n </div>\n ) : (\n <div className={styles.glossaryContent}>\n {/* Letter navigation */}\n <nav className={styles.letterNav}>\n {letters.map(letter => (\n <a key={letter} href={`#letter-${letter}`} className={styles.letterLink}>\n {letter}\n </a>\n ))}\n </nav>\n\n {/* Terms grouped by letter */}\n {letters.map(letter => (\n <section key={letter} id={`letter-${letter}`} className={styles.letterSection}>\n <h2 className={styles.letterHeading}>{letter}</h2>\n <dl className={styles.termList}>\n {groupedTerms[letter].map((term, index) => (\n <div\n key={`${letter}-${index}`}\n className={styles.termItem}\n id={term.id || term.term.toLowerCase().replace(/\\s+/g, '-')}\n >\n <dt className={styles.termName}>\n {term.term}\n {term.abbreviation && (\n <span className={styles.abbreviation}> ({term.abbreviation})</span>\n )}\n </dt>\n <dd className={styles.termDefinition}>\n {term.definition}\n {term.relatedTerms && term.relatedTerms.length > 0 && (\n <div className={styles.relatedTerms}>\n <strong>Related terms:</strong>{' '}\n {term.relatedTerms.map((related, idx) => (\n <React.Fragment key={idx}>\n {idx > 0 && ', '}\n <a href={`#${related.toLowerCase().replace(/\\s+/g, '-')}`}>\n {related}\n </a>\n </React.Fragment>\n ))}\n </div>\n )}\n </dd>\n </div>\n ))}\n </dl>\n </section>\n ))}\n </div>\n )}\n\n <footer className={styles.glossaryFooter}>\n <p>Total terms: {terms.length}</p>\n </footer>\n </div>\n </Layout>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/GlossaryPage.js"],"sourcesContent":["import React, { useState, useMemo } from 'react';\nimport Layout from '@theme/Layout';\nimport useDocusaurusContext from '@docusaurus/useDocusaurusContext';\nimport styles from './GlossaryPage.module.css';\n\n/**\n * Groups glossary terms by their first letter\n */\nfunction groupTermsByLetter(terms) {\n const grouped = {};\n\n terms.forEach(term => {\n const firstLetter = term.term.charAt(0).toUpperCase();\n if (!grouped[firstLetter]) {\n grouped[firstLetter] = [];\n }\n grouped[firstLetter].push(term);\n });\n\n // Sort each group alphabetically\n Object.keys(grouped).forEach(letter => {\n grouped[letter].sort((a, b) => a.term.localeCompare(b.term));\n });\n\n return grouped;\n}\n\n/**\n * GlossaryPage component - displays all glossary terms\n */\nexport default function GlossaryPage({ glossaryData }) {\n useDocusaurusContext();\n const [searchTerm, setSearchTerm] = useState('');\n\n const terms = useMemo(() => glossaryData?.terms || [], [glossaryData?.terms]);\n\n // Filter terms based on search\n const filteredTerms = useMemo(() => {\n if (!searchTerm) return terms;\n\n const lowerSearch = searchTerm.toLowerCase();\n return terms.filter(term => {\n const haystack = [term.term, term.definition, term.abbreviation, ...(term.aliases || [])]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n return haystack.includes(lowerSearch);\n });\n }, [terms, searchTerm]);\n\n // Group terms by first letter\n const groupedTerms = useMemo(() => {\n return groupTermsByLetter(filteredTerms);\n }, [filteredTerms]);\n\n const letters = Object.keys(groupedTerms).sort();\n\n const glossaryTitle = glossaryData?.title || 'Glossary';\n\n return (\n <Layout title={glossaryTitle} description=\"A glossary of terms and definitions\">\n <div className={styles.glossaryContainer}>\n <header className={styles.glossaryHeader}>\n <h1>{glossaryTitle}</h1>\n <p className={styles.glossaryDescription}>\n {glossaryData?.description || 'A collection of terms and their definitions'}\n </p>\n\n <div className={styles.searchContainer}>\n <input\n type=\"text\"\n placeholder=\"Search terms...\"\n className={styles.searchInput}\n value={searchTerm}\n onChange={e => setSearchTerm(e.target.value)}\n />\n </div>\n </header>\n\n {filteredTerms.length === 0 ? (\n <div className={styles.noResults}>\n <p>No terms found matching \"{searchTerm}\"</p>\n </div>\n ) : (\n <div className={styles.glossaryContent}>\n {/* Letter navigation */}\n <nav className={styles.letterNav}>\n {letters.map(letter => (\n <a key={letter} href={`#letter-${letter}`} className={styles.letterLink}>\n {letter}\n </a>\n ))}\n </nav>\n\n {/* Terms grouped by letter */}\n {letters.map(letter => (\n <section key={letter} id={`letter-${letter}`} className={styles.letterSection}>\n <h2 className={styles.letterHeading}>{letter}</h2>\n <dl className={styles.termList}>\n {groupedTerms[letter].map((term, index) => (\n <div\n key={`${letter}-${index}`}\n className={styles.termItem}\n id={term.id || term.term.toLowerCase().replace(/\\s+/g, '-')}\n >\n <dt className={styles.termName}>\n {term.term}\n {term.abbreviation && (\n <span className={styles.abbreviation}> ({term.abbreviation})</span>\n )}\n </dt>\n <dd className={styles.termDefinition}>\n {term.definition}\n {term.relatedTerms && term.relatedTerms.length > 0 && (\n <div className={styles.relatedTerms}>\n <strong>Related terms:</strong>{' '}\n {term.relatedTerms.map((related, idx) => (\n <React.Fragment key={idx}>\n {idx > 0 && ', '}\n <a href={`#${related.toLowerCase().replace(/\\s+/g, '-')}`}>\n {related}\n </a>\n </React.Fragment>\n ))}\n </div>\n )}\n </dd>\n </div>\n ))}\n </dl>\n </section>\n ))}\n </div>\n )}\n\n <footer className={styles.glossaryFooter}>\n <p>Total terms: {terms.length}</p>\n </footer>\n </div>\n </Layout>\n );\n}\n"],"mappings":";AAAA,OAAO,SAAS,UAAU,eAAe;AACzC,OAAO,YAAY;AACnB,OAAO,0BAA0B;AACjC,OAAO,YAAY;AA2DX,SACE,KADF;AAtDR,SAAS,mBAAmB,OAAO;AACjC,QAAM,UAAU,CAAC;AAEjB,QAAM,QAAQ,UAAQ;AACpB,UAAM,cAAc,KAAK,KAAK,OAAO,CAAC,EAAE,YAAY;AACpD,QAAI,CAAC,QAAQ,WAAW,GAAG;AACzB,cAAQ,WAAW,IAAI,CAAC;AAAA,IAC1B;AACA,YAAQ,WAAW,EAAE,KAAK,IAAI;AAAA,EAChC,CAAC;AAGD,SAAO,KAAK,OAAO,EAAE,QAAQ,YAAU;AACrC,YAAQ,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA,EAC7D,CAAC;AAED,SAAO;AACT;AAKe,SAAR,aAA8B,EAAE,aAAa,GAAG;AACrD,uBAAqB;AACrB,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,EAAE;AAE/C,QAAM,QAAQ,QAAQ,MAAM,cAAc,SAAS,CAAC,GAAG,CAAC,cAAc,KAAK,CAAC;AAG5E,QAAM,gBAAgB,QAAQ,MAAM;AAClC,QAAI,CAAC,WAAY,QAAO;AAExB,UAAM,cAAc,WAAW,YAAY;AAC3C,WAAO,MAAM,OAAO,UAAQ;AAC1B,YAAM,WAAW,CAAC,KAAK,MAAM,KAAK,YAAY,KAAK,cAAc,GAAI,KAAK,WAAW,CAAC,CAAE,EACrF,OAAO,OAAO,EACd,KAAK,GAAG,EACR,YAAY;AACf,aAAO,SAAS,SAAS,WAAW;AAAA,IACtC,CAAC;AAAA,EACH,GAAG,CAAC,OAAO,UAAU,CAAC;AAGtB,QAAM,eAAe,QAAQ,MAAM;AACjC,WAAO,mBAAmB,aAAa;AAAA,EACzC,GAAG,CAAC,aAAa,CAAC;AAElB,QAAM,UAAU,OAAO,KAAK,YAAY,EAAE,KAAK;AAE/C,QAAM,gBAAgB,cAAc,SAAS;AAE7C,SACE,oBAAC,UAAO,OAAO,eAAe,aAAY,uCACxC,+BAAC,SAAI,WAAW,OAAO,mBACrB;AAAA,yBAAC,YAAO,WAAW,OAAO,gBACxB;AAAA,0BAAC,QAAI,yBAAc;AAAA,MACnB,oBAAC,OAAE,WAAW,OAAO,qBAClB,wBAAc,eAAe,+CAChC;AAAA,MAEA,oBAAC,SAAI,WAAW,OAAO,iBACrB;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,aAAY;AAAA,UACZ,WAAW,OAAO;AAAA,UAClB,OAAO;AAAA,UACP,UAAU,OAAK,cAAc,EAAE,OAAO,KAAK;AAAA;AAAA,MAC7C,GACF;AAAA,OACF;AAAA,IAEC,cAAc,WAAW,IACxB,oBAAC,SAAI,WAAW,OAAO,WACrB,+BAAC,OAAE;AAAA;AAAA,MAA0B;AAAA,MAAW;AAAA,OAAC,GAC3C,IAEA,qBAAC,SAAI,WAAW,OAAO,iBAErB;AAAA,0BAAC,SAAI,WAAW,OAAO,WACpB,kBAAQ,IAAI,YACX,oBAAC,OAAe,MAAM,WAAW,MAAM,IAAI,WAAW,OAAO,YAC1D,oBADK,MAER,CACD,GACH;AAAA,MAGC,QAAQ,IAAI,YACX,qBAAC,aAAqB,IAAI,UAAU,MAAM,IAAI,WAAW,OAAO,eAC9D;AAAA,4BAAC,QAAG,WAAW,OAAO,eAAgB,kBAAO;AAAA,QAC7C,oBAAC,QAAG,WAAW,OAAO,UACnB,uBAAa,MAAM,EAAE,IAAI,CAAC,MAAM,UAC/B;AAAA,UAAC;AAAA;AAAA,YAEC,WAAW,OAAO;AAAA,YAClB,IAAI,KAAK,MAAM,KAAK,KAAK,YAAY,EAAE,QAAQ,QAAQ,GAAG;AAAA,YAE1D;AAAA,mCAAC,QAAG,WAAW,OAAO,UACnB;AAAA,qBAAK;AAAA,gBACL,KAAK,gBACJ,qBAAC,UAAK,WAAW,OAAO,cAAc;AAAA;AAAA,kBAAG,KAAK;AAAA,kBAAa;AAAA,mBAAC;AAAA,iBAEhE;AAAA,cACA,qBAAC,QAAG,WAAW,OAAO,gBACnB;AAAA,qBAAK;AAAA,gBACL,KAAK,gBAAgB,KAAK,aAAa,SAAS,KAC/C,qBAAC,SAAI,WAAW,OAAO,cACrB;AAAA,sCAAC,YAAO,4BAAc;AAAA,kBAAU;AAAA,kBAC/B,KAAK,aAAa,IAAI,CAAC,SAAS,QAC/B,qBAAC,MAAM,UAAN,EACE;AAAA,0BAAM,KAAK;AAAA,oBACZ,oBAAC,OAAE,MAAM,IAAI,QAAQ,YAAY,EAAE,QAAQ,QAAQ,GAAG,CAAC,IACpD,mBACH;AAAA,uBAJmB,GAKrB,CACD;AAAA,mBACH;AAAA,iBAEJ;AAAA;AAAA;AAAA,UAzBK,GAAG,MAAM,IAAI,KAAK;AAAA,QA0BzB,CACD,GACH;AAAA,WAjCY,MAkCd,CACD;AAAA,OACH;AAAA,IAGF,oBAAC,YAAO,WAAW,OAAO,gBACxB,+BAAC,OAAE;AAAA;AAAA,MAAc,MAAM;AAAA,OAAO,GAChC;AAAA,KACF,GACF;AAEJ;","names":[]}
package/dist/index.cjs CHANGED
@@ -96,7 +96,8 @@ function remarkGlossaryTerms({
96
96
  terms = [],
97
97
  glossaryPath = null,
98
98
  routePath = "/glossary",
99
- siteDir = null
99
+ siteDir = null,
100
+ expandAcronymsOnFirstUse = false
100
101
  } = {}) {
101
102
  let glossaryTerms = terms;
102
103
  if (!glossaryTerms.length && glossaryPath && siteDir) {
@@ -185,7 +186,7 @@ function remarkGlossaryTerms({
185
186
  if (sortedTerms.length === 0) {
186
187
  return (tree) => tree;
187
188
  }
188
- function replaceTermsInText(text) {
189
+ function replaceTermsInText(text, seenTerms) {
189
190
  if (!text || !sortedTerms.length) {
190
191
  return [{ type: "text", value: text }];
191
192
  }
@@ -247,6 +248,8 @@ function remarkGlossaryTerms({
247
248
  value: text.substring(lastIndex, match.index)
248
249
  });
249
250
  }
251
+ const displayText = resolveDisplayText(match, text, seenTerms);
252
+ seenTerms.add(match.termObj.term);
250
253
  result.push({
251
254
  type: "mdxJsxFlowElement",
252
255
  name: "GlossaryTerm",
@@ -270,7 +273,7 @@ function remarkGlossaryTerms({
270
273
  children: [
271
274
  {
272
275
  type: "text",
273
- value: match.originalText
276
+ value: displayText
274
277
  }
275
278
  ]
276
279
  });
@@ -284,6 +287,20 @@ function remarkGlossaryTerms({
284
287
  }
285
288
  return result.length > 0 ? result : [{ type: "text", value: text }];
286
289
  }
290
+ function resolveDisplayText(match, text, seenTerms) {
291
+ const termObj = match.termObj;
292
+ if (!expandAcronymsOnFirstUse) return match.originalText;
293
+ if (!termObj.abbreviation) return match.originalText;
294
+ if (seenTerms.has(termObj.term)) return match.originalText;
295
+ const isCanonical = match.length === termObj.term.length && match.originalText.toLowerCase() === termObj.term.toLowerCase();
296
+ if (!isCanonical) return match.originalText;
297
+ const longForm = termObj.abbreviation;
298
+ const lookbackWindow = longForm.length + 10;
299
+ const lookbackStart = Math.max(0, match.index - lookbackWindow);
300
+ const lookback = text.substring(lookbackStart, match.index).toLowerCase();
301
+ if (lookback.includes(longForm.toLowerCase())) return match.originalText;
302
+ return `${longForm} (${match.originalText})`;
303
+ }
287
304
  function collectHeadingTextNodes(tree) {
288
305
  const skip = /* @__PURE__ */ new WeakSet();
289
306
  (0, import_unist_util_visit.visit)(tree, "heading", (headingNode) => {
@@ -296,6 +313,7 @@ function remarkGlossaryTerms({
296
313
  const transformer = (tree) => {
297
314
  let usedGlossaryTerm = false;
298
315
  const textNodesInHeadings = collectHeadingTextNodes(tree);
316
+ const seenTerms = /* @__PURE__ */ new Set();
299
317
  (0, import_unist_util_visit.visit)(tree, "text", (node, index, parent) => {
300
318
  if (parent.type === "code" || parent.type === "inlineCode" || parent.type === "link" || parent.type === "mdxJsxFlowElement" || parent.type === "mdxJsxTextElement") {
301
319
  return;
@@ -303,7 +321,7 @@ function remarkGlossaryTerms({
303
321
  if (textNodesInHeadings.has(node)) {
304
322
  return;
305
323
  }
306
- const replacements = replaceTermsInText(node.value);
324
+ const replacements = replaceTermsInText(node.value, seenTerms);
307
325
  if (replacements.length > 1 || replacements.length === 1 && replacements[0].type !== "text") {
308
326
  const newNodes = replacements.map((replacement) => {
309
327
  if (replacement.type === "mdxJsxFlowElement") {
@@ -718,14 +736,19 @@ function glossaryPlugin(context, options = {}) {
718
736
  }
719
737
  var remarkPlugin = remarkGlossaryTerms;
720
738
  function getRemarkPlugin(pluginOptions, context) {
721
- const { glossaryPath = "glossary/glossary.json", routePath = "/glossary" } = pluginOptions;
739
+ const {
740
+ glossaryPath = "glossary/glossary.json",
741
+ routePath = "/glossary",
742
+ expandAcronymsOnFirstUse = false
743
+ } = pluginOptions;
722
744
  const siteDir = context?.siteDir;
723
745
  return [
724
746
  remarkGlossaryTerms,
725
747
  {
726
748
  glossaryPath,
727
749
  routePath,
728
- siteDir
750
+ siteDir,
751
+ expandAcronymsOnFirstUse
729
752
  }
730
753
  ];
731
754
  }