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 +23 -6
- package/dist/{chunk-MG34SMDE.js → chunk-CIAA7XR3.js} +23 -5
- package/dist/chunk-CIAA7XR3.js.map +1 -0
- package/dist/{chunk-33WWYCNA.js → chunk-EQBYCLUI.js} +9 -4
- package/dist/chunk-EQBYCLUI.js.map +1 -0
- package/dist/components/GlossaryPage.cjs +4 -3
- package/dist/components/GlossaryPage.cjs.map +1 -1
- package/dist/components/GlossaryPage.js +4 -3
- package/dist/components/GlossaryPage.js.map +1 -1
- package/dist/index.cjs +29 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -97
- package/dist/index.d.ts +15 -97
- package/dist/index.js +2 -2
- package/dist/preset.cjs +38 -8
- package/dist/preset.cjs.map +1 -1
- package/dist/preset.d.cts +1 -1
- package/dist/preset.d.ts +1 -1
- package/dist/preset.js +11 -4
- package/dist/preset.js.map +1 -1
- package/dist/remark/glossary-terms.cjs +22 -4
- package/dist/remark/glossary-terms.cjs.map +1 -1
- package/dist/remark/glossary-terms.d.cts +10 -0
- package/dist/remark/glossary-terms.d.ts +10 -0
- package/dist/remark/glossary-terms.js +1 -1
- package/dist/theme/GlossaryTerm/index.cjs +22 -2
- package/dist/theme/GlossaryTerm/index.cjs.map +1 -1
- package/dist/theme/GlossaryTerm/index.js +22 -2
- package/dist/theme/GlossaryTerm/index.js.map +1 -1
- package/dist/types-B6LWPlBY.d.cts +54 -0
- package/dist/types-B6LWPlBY.d.ts +54 -0
- package/dist/validation.d.cts +46 -2
- package/dist/validation.d.ts +46 -2
- package/package.json +29 -12
- package/dist/chunk-33WWYCNA.js.map +0 -1
- package/dist/chunk-MG34SMDE.js.map +0 -1
|
@@ -1 +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 * @returns {function} Remark plugin function\n */\nexport default function remarkGlossaryTerms({\n terms = [],\n glossaryPath = null,\n routePath = '/glossary',\n siteDir = null,\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 function replaceTermsInText(text) {\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 // 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: match.originalText,\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 // 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 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);\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":["path","fs"]}
|
|
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":["path","fs"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { R as RemarkGlossaryTermsOptions, c as RemarkGlossaryTermsTransformer } from '../types-B6LWPlBY.cjs';
|
|
2
|
+
export { b as GlossaryTerm } from '../types-B6LWPlBY.cjs';
|
|
3
|
+
|
|
4
|
+
declare function remarkGlossaryTerms(
|
|
5
|
+
options?: RemarkGlossaryTermsOptions
|
|
6
|
+
): RemarkGlossaryTermsTransformer;
|
|
7
|
+
|
|
8
|
+
declare function clearGlossaryCache(filePath?: string): void;
|
|
9
|
+
|
|
10
|
+
export { RemarkGlossaryTermsOptions, RemarkGlossaryTermsTransformer, clearGlossaryCache, remarkGlossaryTerms as default };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { R as RemarkGlossaryTermsOptions, c as RemarkGlossaryTermsTransformer } from '../types-B6LWPlBY.js';
|
|
2
|
+
export { b as GlossaryTerm } from '../types-B6LWPlBY.js';
|
|
3
|
+
|
|
4
|
+
declare function remarkGlossaryTerms(
|
|
5
|
+
options?: RemarkGlossaryTermsOptions
|
|
6
|
+
): RemarkGlossaryTermsTransformer;
|
|
7
|
+
|
|
8
|
+
declare function clearGlossaryCache(filePath?: string): void;
|
|
9
|
+
|
|
10
|
+
export { RemarkGlossaryTermsOptions, RemarkGlossaryTermsTransformer, clearGlossaryCache, remarkGlossaryTerms as default };
|
|
@@ -37,7 +37,13 @@ var import_react = __toESM(require("react"), 1);
|
|
|
37
37
|
var import_useGlobalData = require("@docusaurus/useGlobalData");
|
|
38
38
|
var import_styles = __toESM(require("../../styles.module-QQW7ISLV.module.css"), 1);
|
|
39
39
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
40
|
-
function GlossaryTerm({
|
|
40
|
+
function GlossaryTerm({
|
|
41
|
+
term,
|
|
42
|
+
definition,
|
|
43
|
+
abbreviation,
|
|
44
|
+
routePath = "/glossary",
|
|
45
|
+
children
|
|
46
|
+
}) {
|
|
41
47
|
const [showTooltip, setShowTooltip] = (0, import_react.useState)(false);
|
|
42
48
|
const [tooltipStyle, setTooltipStyle] = (0, import_react.useState)(null);
|
|
43
49
|
const [placement, setPlacement] = (0, import_react.useState)("top");
|
|
@@ -98,6 +104,20 @@ function GlossaryTerm({ term, definition, routePath = "/glossary", children }) {
|
|
|
98
104
|
);
|
|
99
105
|
return found && found.definition ? found.definition : void 0;
|
|
100
106
|
}, [definition, pluginData, term]);
|
|
107
|
+
const effectiveAbbreviation = (0, import_react.useMemo)(() => {
|
|
108
|
+
let value = abbreviation;
|
|
109
|
+
if (!value) {
|
|
110
|
+
const terms = pluginData && pluginData.terms || [];
|
|
111
|
+
const found = terms.find(
|
|
112
|
+
(t) => typeof t.term === "string" && t.term.toLowerCase() === String(term).toLowerCase()
|
|
113
|
+
);
|
|
114
|
+
value = found && found.abbreviation;
|
|
115
|
+
}
|
|
116
|
+
if (typeof value !== "string") return void 0;
|
|
117
|
+
const trimmed = value.trim();
|
|
118
|
+
if (!trimmed || trimmed.toLowerCase() === String(term).toLowerCase()) return void 0;
|
|
119
|
+
return trimmed;
|
|
120
|
+
}, [abbreviation, pluginData, term]);
|
|
101
121
|
const effectiveRoutePath = (0, import_react.useMemo)(() => {
|
|
102
122
|
if (routePath && typeof routePath === "string" && routePath.length > 0) return routePath;
|
|
103
123
|
return pluginData && pluginData.routePath || "/glossary";
|
|
@@ -128,7 +148,7 @@ function GlossaryTerm({ term, definition, routePath = "/glossary", children }) {
|
|
|
128
148
|
style: showTooltip && tooltipStyle ? { top: `${tooltipStyle.top}px`, left: `${tooltipStyle.left}px` } : void 0,
|
|
129
149
|
children: [
|
|
130
150
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: term }),
|
|
131
|
-
|
|
151
|
+
effectiveAbbreviation ? ` (${effectiveAbbreviation}). ` : "",
|
|
132
152
|
effectiveDefinition
|
|
133
153
|
]
|
|
134
154
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/theme/GlossaryTerm/index.js"],"sourcesContent":["import React, { useMemo, useState, useRef, useEffect, useCallback } from 'react';\nimport { usePluginData } from '@docusaurus/useGlobalData';\nimport styles from './styles.module.css';\n\n/**\n * GlossaryTerm component - displays an inline term with tooltip\n *\n * Usage:\n * import GlossaryTerm from '@theme/GlossaryTerm';\n *\n * <GlossaryTerm term=\"API\" definition=\"Application Programming Interface\" />\n * or\n * <GlossaryTerm term=\"API\">custom display text</GlossaryTerm>\n *\n * @param {object} props\n * @param {string} props.term - The glossary term\n * @param {string} props.definition - The definition to show in tooltip\n * @param {string} props.routePath - Route path to glossary page (default: '/glossary')\n * @param {React.ReactNode} props.children - Optional custom display text\n */\nexport default function GlossaryTerm({
|
|
1
|
+
{"version":3,"sources":["../../../src/theme/GlossaryTerm/index.js"],"sourcesContent":["import React, { useMemo, useState, useRef, useEffect, useCallback } from 'react';\nimport { usePluginData } from '@docusaurus/useGlobalData';\nimport styles from './styles.module.css';\n\n/**\n * GlossaryTerm component - displays an inline term with tooltip\n *\n * Usage:\n * import GlossaryTerm from '@theme/GlossaryTerm';\n *\n * <GlossaryTerm term=\"API\" definition=\"Application Programming Interface\" />\n * or\n * <GlossaryTerm term=\"API\">custom display text</GlossaryTerm>\n *\n * @param {object} props\n * @param {string} props.term - The glossary term\n * @param {string} props.definition - The definition to show in tooltip\n * @param {string} props.abbreviation - Optional long-form expansion shown in the tooltip\n * @param {string} props.routePath - Route path to glossary page (default: '/glossary')\n * @param {React.ReactNode} props.children - Optional custom display text\n */\nexport default function GlossaryTerm({\n term,\n definition,\n abbreviation,\n routePath = '/glossary',\n children,\n}) {\n const [showTooltip, setShowTooltip] = useState(false);\n const [tooltipStyle, setTooltipStyle] = useState(null);\n const [placement, setPlacement] = useState('top'); // 'top' | 'bottom'\n const wrapperRef = useRef(null);\n const tooltipRef = useRef(null);\n\n const updatePosition = useCallback(() => {\n if (!wrapperRef.current || !tooltipRef.current) return;\n const wrapperRect = wrapperRef.current.getBoundingClientRect();\n const tooltipRect = tooltipRef.current.getBoundingClientRect();\n\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n const preferredGap = 8; // px\n\n // Decide top vs bottom based on available space\n const hasSpaceAbove = wrapperRect.top >= tooltipRect.height + preferredGap;\n const hasSpaceBelow = viewportHeight - wrapperRect.bottom >= tooltipRect.height + preferredGap;\n const nextPlacement = hasSpaceAbove || !hasSpaceBelow ? 'top' : 'bottom';\n\n let top;\n if (nextPlacement === 'top') {\n top = wrapperRect.top - tooltipRect.height - preferredGap;\n } else {\n top = wrapperRect.bottom + preferredGap;\n }\n\n // Center horizontally on the wrapper, then clamp within viewport with margin\n const horizontalMargin = 8;\n let left = wrapperRect.left + wrapperRect.width / 2 - tooltipRect.width / 2;\n left = Math.max(\n horizontalMargin,\n Math.min(left, viewportWidth - tooltipRect.width - horizontalMargin)\n );\n\n setPlacement(nextPlacement);\n setTooltipStyle({ top: Math.max(4, top), left });\n }, []);\n\n useEffect(() => {\n if (!showTooltip) return;\n\n // Use double requestAnimationFrame to ensure DOM is fully rendered and layout is complete\n // This ensures tooltipRef.current is available and has proper dimensions\n let rafId2;\n const rafId1 = requestAnimationFrame(() => {\n rafId2 = requestAnimationFrame(() => {\n updatePosition();\n });\n });\n\n const onScroll = () => updatePosition();\n const onResize = () => updatePosition();\n window.addEventListener('scroll', onScroll, true);\n window.addEventListener('resize', onResize);\n return () => {\n cancelAnimationFrame(rafId1);\n if (rafId2) cancelAnimationFrame(rafId2);\n window.removeEventListener('scroll', onScroll, true);\n window.removeEventListener('resize', onResize);\n };\n }, [showTooltip, updatePosition]);\n\n // Pull definition/route from plugin global data if not provided\n const pluginData = usePluginData('docusaurus-plugin-glossary');\n const effectiveDefinition = useMemo(() => {\n if (definition && typeof definition === 'string' && definition.length > 0) {\n return definition;\n }\n const terms = (pluginData && pluginData.terms) || [];\n const found = terms.find(\n t => typeof t.term === 'string' && t.term.toLowerCase() === String(term).toLowerCase()\n );\n return found && found.definition ? found.definition : undefined;\n }, [definition, pluginData, term]);\n\n // Pull the long-form expansion (abbreviation) from the prop, falling back to\n // the plugin global data. Skipped when it is empty or identical to the term.\n const effectiveAbbreviation = useMemo(() => {\n let value = abbreviation;\n if (!value) {\n const terms = (pluginData && pluginData.terms) || [];\n const found = terms.find(\n t => typeof t.term === 'string' && t.term.toLowerCase() === String(term).toLowerCase()\n );\n value = found && found.abbreviation;\n }\n if (typeof value !== 'string') return undefined;\n const trimmed = value.trim();\n if (!trimmed || trimmed.toLowerCase() === String(term).toLowerCase()) return undefined;\n return trimmed;\n }, [abbreviation, pluginData, term]);\n\n const effectiveRoutePath = useMemo(() => {\n if (routePath && typeof routePath === 'string' && routePath.length > 0) return routePath;\n return (pluginData && pluginData.routePath) || '/glossary';\n }, [pluginData, routePath]);\n\n const displayText = children || term;\n const termId = term.toLowerCase().replace(/\\s+/g, '-');\n\n return (\n <span ref={wrapperRef} className={styles.glossaryTermWrapper}>\n <a\n href={`${effectiveRoutePath}#${termId}`}\n className={styles.glossaryTerm}\n onMouseEnter={() => setShowTooltip(true)}\n onMouseLeave={() => setShowTooltip(false)}\n onFocus={() => setShowTooltip(true)}\n onBlur={() => setShowTooltip(false)}\n aria-describedby={`tooltip-${termId}`}\n >\n {displayText}\n </a>\n {effectiveDefinition && (\n <span\n ref={tooltipRef}\n id={`tooltip-${termId}`}\n className={\n `${styles.tooltip} ${showTooltip ? styles.tooltipVisible : ''} ` +\n `${placement === 'top' ? styles.tooltipTop : styles.tooltipBottom} ` +\n `${styles.tooltipFloating}`\n }\n role=\"tooltip\"\n style={\n showTooltip && tooltipStyle\n ? { top: `${tooltipStyle.top}px`, left: `${tooltipStyle.left}px` }\n : undefined\n }\n >\n <strong>{term}</strong>\n {effectiveAbbreviation ? ` (${effectiveAbbreviation}). ` : ''}{effectiveDefinition}\n </span>\n )}\n </span>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAyE;AACzE,2BAA8B;AAC9B,oBAAmB;AAkIb;AA/GS,SAAR,aAA8B;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AACF,GAAG;AACD,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAS,KAAK;AACpD,QAAM,CAAC,cAAc,eAAe,QAAI,uBAAS,IAAI;AACrD,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,KAAK;AAChD,QAAM,iBAAa,qBAAO,IAAI;AAC9B,QAAM,iBAAa,qBAAO,IAAI;AAE9B,QAAM,qBAAiB,0BAAY,MAAM;AACvC,QAAI,CAAC,WAAW,WAAW,CAAC,WAAW,QAAS;AAChD,UAAM,cAAc,WAAW,QAAQ,sBAAsB;AAC7D,UAAM,cAAc,WAAW,QAAQ,sBAAsB;AAE7D,UAAM,gBAAgB,OAAO;AAC7B,UAAM,iBAAiB,OAAO;AAE9B,UAAM,eAAe;AAGrB,UAAM,gBAAgB,YAAY,OAAO,YAAY,SAAS;AAC9D,UAAM,gBAAgB,iBAAiB,YAAY,UAAU,YAAY,SAAS;AAClF,UAAM,gBAAgB,iBAAiB,CAAC,gBAAgB,QAAQ;AAEhE,QAAI;AACJ,QAAI,kBAAkB,OAAO;AAC3B,YAAM,YAAY,MAAM,YAAY,SAAS;AAAA,IAC/C,OAAO;AACL,YAAM,YAAY,SAAS;AAAA,IAC7B;AAGA,UAAM,mBAAmB;AACzB,QAAI,OAAO,YAAY,OAAO,YAAY,QAAQ,IAAI,YAAY,QAAQ;AAC1E,WAAO,KAAK;AAAA,MACV;AAAA,MACA,KAAK,IAAI,MAAM,gBAAgB,YAAY,QAAQ,gBAAgB;AAAA,IACrE;AAEA,iBAAa,aAAa;AAC1B,oBAAgB,EAAE,KAAK,KAAK,IAAI,GAAG,GAAG,GAAG,KAAK,CAAC;AAAA,EACjD,GAAG,CAAC,CAAC;AAEL,8BAAU,MAAM;AACd,QAAI,CAAC,YAAa;AAIlB,QAAI;AACJ,UAAM,SAAS,sBAAsB,MAAM;AACzC,eAAS,sBAAsB,MAAM;AACnC,uBAAe;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAED,UAAM,WAAW,MAAM,eAAe;AACtC,UAAM,WAAW,MAAM,eAAe;AACtC,WAAO,iBAAiB,UAAU,UAAU,IAAI;AAChD,WAAO,iBAAiB,UAAU,QAAQ;AAC1C,WAAO,MAAM;AACX,2BAAqB,MAAM;AAC3B,UAAI,OAAQ,sBAAqB,MAAM;AACvC,aAAO,oBAAoB,UAAU,UAAU,IAAI;AACnD,aAAO,oBAAoB,UAAU,QAAQ;AAAA,IAC/C;AAAA,EACF,GAAG,CAAC,aAAa,cAAc,CAAC;AAGhC,QAAM,iBAAa,oCAAc,4BAA4B;AAC7D,QAAM,0BAAsB,sBAAQ,MAAM;AACxC,QAAI,cAAc,OAAO,eAAe,YAAY,WAAW,SAAS,GAAG;AACzE,aAAO;AAAA,IACT;AACA,UAAM,QAAS,cAAc,WAAW,SAAU,CAAC;AACnD,UAAM,QAAQ,MAAM;AAAA,MAClB,OAAK,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,YAAY,MAAM,OAAO,IAAI,EAAE,YAAY;AAAA,IACvF;AACA,WAAO,SAAS,MAAM,aAAa,MAAM,aAAa;AAAA,EACxD,GAAG,CAAC,YAAY,YAAY,IAAI,CAAC;AAIjC,QAAM,4BAAwB,sBAAQ,MAAM;AAC1C,QAAI,QAAQ;AACZ,QAAI,CAAC,OAAO;AACV,YAAM,QAAS,cAAc,WAAW,SAAU,CAAC;AACnD,YAAM,QAAQ,MAAM;AAAA,QAClB,OAAK,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,YAAY,MAAM,OAAO,IAAI,EAAE,YAAY;AAAA,MACvF;AACA,cAAQ,SAAS,MAAM;AAAA,IACzB;AACA,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,WAAW,QAAQ,YAAY,MAAM,OAAO,IAAI,EAAE,YAAY,EAAG,QAAO;AAC7E,WAAO;AAAA,EACT,GAAG,CAAC,cAAc,YAAY,IAAI,CAAC;AAEnC,QAAM,yBAAqB,sBAAQ,MAAM;AACvC,QAAI,aAAa,OAAO,cAAc,YAAY,UAAU,SAAS,EAAG,QAAO;AAC/E,WAAQ,cAAc,WAAW,aAAc;AAAA,EACjD,GAAG,CAAC,YAAY,SAAS,CAAC;AAE1B,QAAM,cAAc,YAAY;AAChC,QAAM,SAAS,KAAK,YAAY,EAAE,QAAQ,QAAQ,GAAG;AAErD,SACE,6CAAC,UAAK,KAAK,YAAY,WAAW,cAAAA,QAAO,qBACvC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,GAAG,kBAAkB,IAAI,MAAM;AAAA,QACrC,WAAW,cAAAA,QAAO;AAAA,QAClB,cAAc,MAAM,eAAe,IAAI;AAAA,QACvC,cAAc,MAAM,eAAe,KAAK;AAAA,QACxC,SAAS,MAAM,eAAe,IAAI;AAAA,QAClC,QAAQ,MAAM,eAAe,KAAK;AAAA,QAClC,oBAAkB,WAAW,MAAM;AAAA,QAElC;AAAA;AAAA,IACH;AAAA,IACC,uBACC;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,IAAI,WAAW,MAAM;AAAA,QACrB,WACE,GAAG,cAAAA,QAAO,OAAO,IAAI,cAAc,cAAAA,QAAO,iBAAiB,EAAE,IAC1D,cAAc,QAAQ,cAAAA,QAAO,aAAa,cAAAA,QAAO,aAAa,IAC9D,cAAAA,QAAO,eAAe;AAAA,QAE3B,MAAK;AAAA,QACL,OACE,eAAe,eACX,EAAE,KAAK,GAAG,aAAa,GAAG,MAAM,MAAM,GAAG,aAAa,IAAI,KAAK,IAC/D;AAAA,QAGN;AAAA,sDAAC,YAAQ,gBAAK;AAAA,UACb,wBAAwB,KAAK,qBAAqB,QAAQ;AAAA,UAAI;AAAA;AAAA;AAAA,IACjE;AAAA,KAEJ;AAEJ;","names":["styles"]}
|
|
@@ -3,7 +3,13 @@ import React, { useMemo, useState, useRef, useEffect, useCallback } from "react"
|
|
|
3
3
|
import { usePluginData } from "@docusaurus/useGlobalData";
|
|
4
4
|
import styles from "../../styles.module-QQW7ISLV.module.css";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
function GlossaryTerm({
|
|
6
|
+
function GlossaryTerm({
|
|
7
|
+
term,
|
|
8
|
+
definition,
|
|
9
|
+
abbreviation,
|
|
10
|
+
routePath = "/glossary",
|
|
11
|
+
children
|
|
12
|
+
}) {
|
|
7
13
|
const [showTooltip, setShowTooltip] = useState(false);
|
|
8
14
|
const [tooltipStyle, setTooltipStyle] = useState(null);
|
|
9
15
|
const [placement, setPlacement] = useState("top");
|
|
@@ -64,6 +70,20 @@ function GlossaryTerm({ term, definition, routePath = "/glossary", children }) {
|
|
|
64
70
|
);
|
|
65
71
|
return found && found.definition ? found.definition : void 0;
|
|
66
72
|
}, [definition, pluginData, term]);
|
|
73
|
+
const effectiveAbbreviation = useMemo(() => {
|
|
74
|
+
let value = abbreviation;
|
|
75
|
+
if (!value) {
|
|
76
|
+
const terms = pluginData && pluginData.terms || [];
|
|
77
|
+
const found = terms.find(
|
|
78
|
+
(t) => typeof t.term === "string" && t.term.toLowerCase() === String(term).toLowerCase()
|
|
79
|
+
);
|
|
80
|
+
value = found && found.abbreviation;
|
|
81
|
+
}
|
|
82
|
+
if (typeof value !== "string") return void 0;
|
|
83
|
+
const trimmed = value.trim();
|
|
84
|
+
if (!trimmed || trimmed.toLowerCase() === String(term).toLowerCase()) return void 0;
|
|
85
|
+
return trimmed;
|
|
86
|
+
}, [abbreviation, pluginData, term]);
|
|
67
87
|
const effectiveRoutePath = useMemo(() => {
|
|
68
88
|
if (routePath && typeof routePath === "string" && routePath.length > 0) return routePath;
|
|
69
89
|
return pluginData && pluginData.routePath || "/glossary";
|
|
@@ -94,7 +114,7 @@ function GlossaryTerm({ term, definition, routePath = "/glossary", children }) {
|
|
|
94
114
|
style: showTooltip && tooltipStyle ? { top: `${tooltipStyle.top}px`, left: `${tooltipStyle.left}px` } : void 0,
|
|
95
115
|
children: [
|
|
96
116
|
/* @__PURE__ */ jsx("strong", { children: term }),
|
|
97
|
-
|
|
117
|
+
effectiveAbbreviation ? ` (${effectiveAbbreviation}). ` : "",
|
|
98
118
|
effectiveDefinition
|
|
99
119
|
]
|
|
100
120
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/theme/GlossaryTerm/index.js"],"sourcesContent":["import React, { useMemo, useState, useRef, useEffect, useCallback } from 'react';\nimport { usePluginData } from '@docusaurus/useGlobalData';\nimport styles from './styles.module.css';\n\n/**\n * GlossaryTerm component - displays an inline term with tooltip\n *\n * Usage:\n * import GlossaryTerm from '@theme/GlossaryTerm';\n *\n * <GlossaryTerm term=\"API\" definition=\"Application Programming Interface\" />\n * or\n * <GlossaryTerm term=\"API\">custom display text</GlossaryTerm>\n *\n * @param {object} props\n * @param {string} props.term - The glossary term\n * @param {string} props.definition - The definition to show in tooltip\n * @param {string} props.routePath - Route path to glossary page (default: '/glossary')\n * @param {React.ReactNode} props.children - Optional custom display text\n */\nexport default function GlossaryTerm({
|
|
1
|
+
{"version":3,"sources":["../../../src/theme/GlossaryTerm/index.js"],"sourcesContent":["import React, { useMemo, useState, useRef, useEffect, useCallback } from 'react';\nimport { usePluginData } from '@docusaurus/useGlobalData';\nimport styles from './styles.module.css';\n\n/**\n * GlossaryTerm component - displays an inline term with tooltip\n *\n * Usage:\n * import GlossaryTerm from '@theme/GlossaryTerm';\n *\n * <GlossaryTerm term=\"API\" definition=\"Application Programming Interface\" />\n * or\n * <GlossaryTerm term=\"API\">custom display text</GlossaryTerm>\n *\n * @param {object} props\n * @param {string} props.term - The glossary term\n * @param {string} props.definition - The definition to show in tooltip\n * @param {string} props.abbreviation - Optional long-form expansion shown in the tooltip\n * @param {string} props.routePath - Route path to glossary page (default: '/glossary')\n * @param {React.ReactNode} props.children - Optional custom display text\n */\nexport default function GlossaryTerm({\n term,\n definition,\n abbreviation,\n routePath = '/glossary',\n children,\n}) {\n const [showTooltip, setShowTooltip] = useState(false);\n const [tooltipStyle, setTooltipStyle] = useState(null);\n const [placement, setPlacement] = useState('top'); // 'top' | 'bottom'\n const wrapperRef = useRef(null);\n const tooltipRef = useRef(null);\n\n const updatePosition = useCallback(() => {\n if (!wrapperRef.current || !tooltipRef.current) return;\n const wrapperRect = wrapperRef.current.getBoundingClientRect();\n const tooltipRect = tooltipRef.current.getBoundingClientRect();\n\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n const preferredGap = 8; // px\n\n // Decide top vs bottom based on available space\n const hasSpaceAbove = wrapperRect.top >= tooltipRect.height + preferredGap;\n const hasSpaceBelow = viewportHeight - wrapperRect.bottom >= tooltipRect.height + preferredGap;\n const nextPlacement = hasSpaceAbove || !hasSpaceBelow ? 'top' : 'bottom';\n\n let top;\n if (nextPlacement === 'top') {\n top = wrapperRect.top - tooltipRect.height - preferredGap;\n } else {\n top = wrapperRect.bottom + preferredGap;\n }\n\n // Center horizontally on the wrapper, then clamp within viewport with margin\n const horizontalMargin = 8;\n let left = wrapperRect.left + wrapperRect.width / 2 - tooltipRect.width / 2;\n left = Math.max(\n horizontalMargin,\n Math.min(left, viewportWidth - tooltipRect.width - horizontalMargin)\n );\n\n setPlacement(nextPlacement);\n setTooltipStyle({ top: Math.max(4, top), left });\n }, []);\n\n useEffect(() => {\n if (!showTooltip) return;\n\n // Use double requestAnimationFrame to ensure DOM is fully rendered and layout is complete\n // This ensures tooltipRef.current is available and has proper dimensions\n let rafId2;\n const rafId1 = requestAnimationFrame(() => {\n rafId2 = requestAnimationFrame(() => {\n updatePosition();\n });\n });\n\n const onScroll = () => updatePosition();\n const onResize = () => updatePosition();\n window.addEventListener('scroll', onScroll, true);\n window.addEventListener('resize', onResize);\n return () => {\n cancelAnimationFrame(rafId1);\n if (rafId2) cancelAnimationFrame(rafId2);\n window.removeEventListener('scroll', onScroll, true);\n window.removeEventListener('resize', onResize);\n };\n }, [showTooltip, updatePosition]);\n\n // Pull definition/route from plugin global data if not provided\n const pluginData = usePluginData('docusaurus-plugin-glossary');\n const effectiveDefinition = useMemo(() => {\n if (definition && typeof definition === 'string' && definition.length > 0) {\n return definition;\n }\n const terms = (pluginData && pluginData.terms) || [];\n const found = terms.find(\n t => typeof t.term === 'string' && t.term.toLowerCase() === String(term).toLowerCase()\n );\n return found && found.definition ? found.definition : undefined;\n }, [definition, pluginData, term]);\n\n // Pull the long-form expansion (abbreviation) from the prop, falling back to\n // the plugin global data. Skipped when it is empty or identical to the term.\n const effectiveAbbreviation = useMemo(() => {\n let value = abbreviation;\n if (!value) {\n const terms = (pluginData && pluginData.terms) || [];\n const found = terms.find(\n t => typeof t.term === 'string' && t.term.toLowerCase() === String(term).toLowerCase()\n );\n value = found && found.abbreviation;\n }\n if (typeof value !== 'string') return undefined;\n const trimmed = value.trim();\n if (!trimmed || trimmed.toLowerCase() === String(term).toLowerCase()) return undefined;\n return trimmed;\n }, [abbreviation, pluginData, term]);\n\n const effectiveRoutePath = useMemo(() => {\n if (routePath && typeof routePath === 'string' && routePath.length > 0) return routePath;\n return (pluginData && pluginData.routePath) || '/glossary';\n }, [pluginData, routePath]);\n\n const displayText = children || term;\n const termId = term.toLowerCase().replace(/\\s+/g, '-');\n\n return (\n <span ref={wrapperRef} className={styles.glossaryTermWrapper}>\n <a\n href={`${effectiveRoutePath}#${termId}`}\n className={styles.glossaryTerm}\n onMouseEnter={() => setShowTooltip(true)}\n onMouseLeave={() => setShowTooltip(false)}\n onFocus={() => setShowTooltip(true)}\n onBlur={() => setShowTooltip(false)}\n aria-describedby={`tooltip-${termId}`}\n >\n {displayText}\n </a>\n {effectiveDefinition && (\n <span\n ref={tooltipRef}\n id={`tooltip-${termId}`}\n className={\n `${styles.tooltip} ${showTooltip ? styles.tooltipVisible : ''} ` +\n `${placement === 'top' ? styles.tooltipTop : styles.tooltipBottom} ` +\n `${styles.tooltipFloating}`\n }\n role=\"tooltip\"\n style={\n showTooltip && tooltipStyle\n ? { top: `${tooltipStyle.top}px`, left: `${tooltipStyle.left}px` }\n : undefined\n }\n >\n <strong>{term}</strong>\n {effectiveAbbreviation ? ` (${effectiveAbbreviation}). ` : ''}{effectiveDefinition}\n </span>\n )}\n </span>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for docusaurus-plugin-glossary.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the *declared* public data shapes. Both the plugin
|
|
5
|
+
* entry point (`src/index.ts`) and the remark plugin declaration
|
|
6
|
+
* (`src/remark/glossary-terms.d.ts`) consume these, so the declarations can't
|
|
7
|
+
* drift from each other.
|
|
8
|
+
*
|
|
9
|
+
* Note: the remark runtime (`src/remark/glossary-terms.js`) is plain JS with
|
|
10
|
+
* `checkJs: false`, so it is NOT type-checked against these shapes — keep it in
|
|
11
|
+
* sync by hand.
|
|
12
|
+
*/
|
|
13
|
+
/** A single glossary entry. */
|
|
14
|
+
interface GlossaryTerm {
|
|
15
|
+
term: string;
|
|
16
|
+
definition: string;
|
|
17
|
+
abbreviation?: string;
|
|
18
|
+
relatedTerms?: string[];
|
|
19
|
+
id?: string;
|
|
20
|
+
autoLink?: boolean;
|
|
21
|
+
aliases?: string[];
|
|
22
|
+
caseSensitive?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Shape of a glossary JSON file. */
|
|
25
|
+
interface GlossaryData {
|
|
26
|
+
title?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
terms: GlossaryTerm[];
|
|
29
|
+
}
|
|
30
|
+
/** Options accepted by the glossary plugin / preset. */
|
|
31
|
+
interface GlossaryPluginOptions {
|
|
32
|
+
glossaryPath?: string;
|
|
33
|
+
routePath?: string;
|
|
34
|
+
autoLinkTerms?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* When true, the first canonical occurrence of any term that has an `abbreviation` is
|
|
37
|
+
* rendered as "Long Form (Term)" instead of just "Term", enforcing the convention of
|
|
38
|
+
* introducing an acronym on first use. Subsequent occurrences in the same file render
|
|
39
|
+
* normally. Default: false.
|
|
40
|
+
*/
|
|
41
|
+
expandAcronymsOnFirstUse?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Options accepted by the `remark/glossary-terms` plugin. */
|
|
44
|
+
interface RemarkGlossaryTermsOptions {
|
|
45
|
+
terms?: GlossaryTerm[];
|
|
46
|
+
glossaryPath?: string | null;
|
|
47
|
+
routePath?: string;
|
|
48
|
+
siteDir?: string | null;
|
|
49
|
+
expandAcronymsOnFirstUse?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** The transformer returned by the remark plugin factory. */
|
|
52
|
+
type RemarkGlossaryTermsTransformer = (tree: unknown) => unknown;
|
|
53
|
+
|
|
54
|
+
export type { GlossaryPluginOptions as G, RemarkGlossaryTermsOptions as R, GlossaryData as a, GlossaryTerm as b, RemarkGlossaryTermsTransformer as c };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for docusaurus-plugin-glossary.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the *declared* public data shapes. Both the plugin
|
|
5
|
+
* entry point (`src/index.ts`) and the remark plugin declaration
|
|
6
|
+
* (`src/remark/glossary-terms.d.ts`) consume these, so the declarations can't
|
|
7
|
+
* drift from each other.
|
|
8
|
+
*
|
|
9
|
+
* Note: the remark runtime (`src/remark/glossary-terms.js`) is plain JS with
|
|
10
|
+
* `checkJs: false`, so it is NOT type-checked against these shapes — keep it in
|
|
11
|
+
* sync by hand.
|
|
12
|
+
*/
|
|
13
|
+
/** A single glossary entry. */
|
|
14
|
+
interface GlossaryTerm {
|
|
15
|
+
term: string;
|
|
16
|
+
definition: string;
|
|
17
|
+
abbreviation?: string;
|
|
18
|
+
relatedTerms?: string[];
|
|
19
|
+
id?: string;
|
|
20
|
+
autoLink?: boolean;
|
|
21
|
+
aliases?: string[];
|
|
22
|
+
caseSensitive?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Shape of a glossary JSON file. */
|
|
25
|
+
interface GlossaryData {
|
|
26
|
+
title?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
terms: GlossaryTerm[];
|
|
29
|
+
}
|
|
30
|
+
/** Options accepted by the glossary plugin / preset. */
|
|
31
|
+
interface GlossaryPluginOptions {
|
|
32
|
+
glossaryPath?: string;
|
|
33
|
+
routePath?: string;
|
|
34
|
+
autoLinkTerms?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* When true, the first canonical occurrence of any term that has an `abbreviation` is
|
|
37
|
+
* rendered as "Long Form (Term)" instead of just "Term", enforcing the convention of
|
|
38
|
+
* introducing an acronym on first use. Subsequent occurrences in the same file render
|
|
39
|
+
* normally. Default: false.
|
|
40
|
+
*/
|
|
41
|
+
expandAcronymsOnFirstUse?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Options accepted by the `remark/glossary-terms` plugin. */
|
|
44
|
+
interface RemarkGlossaryTermsOptions {
|
|
45
|
+
terms?: GlossaryTerm[];
|
|
46
|
+
glossaryPath?: string | null;
|
|
47
|
+
routePath?: string;
|
|
48
|
+
siteDir?: string | null;
|
|
49
|
+
expandAcronymsOnFirstUse?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** The transformer returned by the remark plugin factory. */
|
|
52
|
+
type RemarkGlossaryTermsTransformer = (tree: unknown) => unknown;
|
|
53
|
+
|
|
54
|
+
export type { GlossaryPluginOptions as G, RemarkGlossaryTermsOptions as R, GlossaryData as a, GlossaryTerm as b, RemarkGlossaryTermsTransformer as c };
|
package/dist/validation.d.cts
CHANGED
|
@@ -1,2 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { a as GlossaryData } from './types-B6LWPlBY.cjs';
|
|
2
|
+
|
|
3
|
+
interface ValidationError {
|
|
4
|
+
field: string;
|
|
5
|
+
message: string;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
}
|
|
8
|
+
interface ValidationResult {
|
|
9
|
+
valid: boolean;
|
|
10
|
+
errors: ValidationError[];
|
|
11
|
+
data: GlossaryData;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validates glossary data structure
|
|
15
|
+
*
|
|
16
|
+
* Ensures the glossary data conforms to the expected schema:
|
|
17
|
+
* - Must be an object with a "terms" array
|
|
18
|
+
* - Each term must have "term" (string) and "definition" (string)
|
|
19
|
+
* - Optional fields: abbreviation (string), relatedTerms (string[]), id (string)
|
|
20
|
+
*
|
|
21
|
+
* @param data - The data to validate
|
|
22
|
+
* @param options - Validation options
|
|
23
|
+
* @param options.throwOnError - If true, throws an error on validation failure (default: true)
|
|
24
|
+
* @returns Validation result with errors and sanitized data
|
|
25
|
+
* @throws Error if data is invalid and throwOnError is true
|
|
26
|
+
*/
|
|
27
|
+
declare function validateGlossaryData(data: unknown, options?: {
|
|
28
|
+
throwOnError?: boolean;
|
|
29
|
+
}): ValidationResult;
|
|
30
|
+
/**
|
|
31
|
+
* Custom error class for glossary validation errors
|
|
32
|
+
* Provides detailed error messages for debugging
|
|
33
|
+
*/
|
|
34
|
+
declare class GlossaryValidationError extends Error {
|
|
35
|
+
readonly errors: ValidationError[];
|
|
36
|
+
constructor(errors: ValidationError[]);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Formats validation errors into a readable string
|
|
40
|
+
*
|
|
41
|
+
* @param errors - Array of validation errors
|
|
42
|
+
* @returns Formatted error message
|
|
43
|
+
*/
|
|
44
|
+
declare function formatValidationErrors(errors: ValidationError[]): string;
|
|
45
|
+
|
|
46
|
+
export { GlossaryValidationError, type ValidationError, type ValidationResult, formatValidationErrors, validateGlossaryData };
|
package/dist/validation.d.ts
CHANGED
|
@@ -1,2 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { a as GlossaryData } from './types-B6LWPlBY.js';
|
|
2
|
+
|
|
3
|
+
interface ValidationError {
|
|
4
|
+
field: string;
|
|
5
|
+
message: string;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
}
|
|
8
|
+
interface ValidationResult {
|
|
9
|
+
valid: boolean;
|
|
10
|
+
errors: ValidationError[];
|
|
11
|
+
data: GlossaryData;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validates glossary data structure
|
|
15
|
+
*
|
|
16
|
+
* Ensures the glossary data conforms to the expected schema:
|
|
17
|
+
* - Must be an object with a "terms" array
|
|
18
|
+
* - Each term must have "term" (string) and "definition" (string)
|
|
19
|
+
* - Optional fields: abbreviation (string), relatedTerms (string[]), id (string)
|
|
20
|
+
*
|
|
21
|
+
* @param data - The data to validate
|
|
22
|
+
* @param options - Validation options
|
|
23
|
+
* @param options.throwOnError - If true, throws an error on validation failure (default: true)
|
|
24
|
+
* @returns Validation result with errors and sanitized data
|
|
25
|
+
* @throws Error if data is invalid and throwOnError is true
|
|
26
|
+
*/
|
|
27
|
+
declare function validateGlossaryData(data: unknown, options?: {
|
|
28
|
+
throwOnError?: boolean;
|
|
29
|
+
}): ValidationResult;
|
|
30
|
+
/**
|
|
31
|
+
* Custom error class for glossary validation errors
|
|
32
|
+
* Provides detailed error messages for debugging
|
|
33
|
+
*/
|
|
34
|
+
declare class GlossaryValidationError extends Error {
|
|
35
|
+
readonly errors: ValidationError[];
|
|
36
|
+
constructor(errors: ValidationError[]);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Formats validation errors into a readable string
|
|
40
|
+
*
|
|
41
|
+
* @param errors - Array of validation errors
|
|
42
|
+
* @returns Formatted error message
|
|
43
|
+
*/
|
|
44
|
+
declare function formatValidationErrors(errors: ValidationError[]): string;
|
|
45
|
+
|
|
46
|
+
export { GlossaryValidationError, type ValidationError, type ValidationResult, formatValidationErrors, validateGlossaryData };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docusaurus-plugin-glossary",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.4",
|
|
4
4
|
"description": "A Docusaurus plugin for creating and managing glossary terms with auto-generated pages and inline tooltips",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -8,19 +8,34 @@
|
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
14
19
|
},
|
|
15
20
|
"./preset": {
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/preset.d.ts",
|
|
23
|
+
"default": "./dist/preset.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/preset.d.cts",
|
|
27
|
+
"default": "./dist/preset.cjs"
|
|
28
|
+
}
|
|
19
29
|
},
|
|
20
30
|
"./remark/glossary-terms": {
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/remark/glossary-terms.d.ts",
|
|
33
|
+
"default": "./dist/remark/glossary-terms.js"
|
|
34
|
+
},
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "./dist/remark/glossary-terms.d.cts",
|
|
37
|
+
"default": "./dist/remark/glossary-terms.cjs"
|
|
38
|
+
}
|
|
24
39
|
}
|
|
25
40
|
},
|
|
26
41
|
"files": [
|
|
@@ -34,6 +49,7 @@
|
|
|
34
49
|
"watch": "npm run dev",
|
|
35
50
|
"clean": "rimraf dist",
|
|
36
51
|
"typecheck": "tsc --noEmit",
|
|
52
|
+
"check:exports": "npm run build && attw --pack --profile node16 .",
|
|
37
53
|
"test": "jest",
|
|
38
54
|
"test:watch": "jest --watch",
|
|
39
55
|
"test:coverage": "jest --coverage",
|
|
@@ -44,7 +60,7 @@
|
|
|
44
60
|
"example:serve": "npm --prefix examples/docusaurus-v3 run serve",
|
|
45
61
|
"example:clear": "npm --prefix examples/docusaurus-v3 run clear",
|
|
46
62
|
"prepare": "husky",
|
|
47
|
-
"prepublishOnly": "npm run build && npm test",
|
|
63
|
+
"prepublishOnly": "npm run build && npm test && npm run check:exports",
|
|
48
64
|
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\"",
|
|
49
65
|
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,md}\"",
|
|
50
66
|
"lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" \"__tests__/**/*.js\"",
|
|
@@ -80,9 +96,10 @@
|
|
|
80
96
|
"validate-peer-dependencies": "^2.2.0"
|
|
81
97
|
},
|
|
82
98
|
"engines": {
|
|
83
|
-
"node": ">=
|
|
99
|
+
"node": ">=20.0"
|
|
84
100
|
},
|
|
85
101
|
"devDependencies": {
|
|
102
|
+
"@arethetypeswrong/cli": "^0.18.4",
|
|
86
103
|
"@babel/preset-env": "^7.28.5",
|
|
87
104
|
"@babel/preset-react": "^7.28.5",
|
|
88
105
|
"@babel/preset-typescript": "^7.28.5",
|