docusaurus-plugin-glossary 3.0.2 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-4CUFUKUA.js +109 -0
- package/dist/chunk-4CUFUKUA.js.map +1 -0
- package/dist/chunk-PEB4Y6RI.js +311 -0
- package/dist/chunk-PEB4Y6RI.js.map +1 -0
- package/dist/chunk-SNP37IVL.js +212 -0
- package/dist/chunk-SNP37IVL.js.map +1 -0
- package/dist/client/index.cjs +55 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.js +10 -21
- package/dist/client/index.js.map +1 -0
- package/dist/components/GlossaryPage.cjs +130 -0
- package/dist/components/GlossaryPage.cjs.map +1 -0
- package/dist/components/GlossaryPage.js +74 -113
- package/dist/components/GlossaryPage.js.map +1 -0
- package/dist/index.cjs +659 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +173 -0
- package/dist/index.d.ts +83 -11
- package/dist/index.js +23 -173
- package/dist/index.js.map +1 -0
- package/dist/preset.cjs +710 -0
- package/dist/preset.cjs.map +1 -0
- package/dist/preset.d.cts +98 -0
- package/dist/preset.d.ts +8 -7
- package/dist/preset.js +79 -143
- package/dist/preset.js.map +1 -0
- package/dist/remark/glossary-terms.cjs +345 -0
- package/dist/remark/glossary-terms.cjs.map +1 -0
- package/dist/remark/glossary-terms.js +9 -440
- package/dist/remark/glossary-terms.js.map +1 -0
- package/dist/theme/GlossaryTerm/index.cjs +138 -0
- package/dist/theme/GlossaryTerm/index.cjs.map +1 -0
- package/dist/theme/GlossaryTerm/index.js +56 -90
- package/dist/theme/GlossaryTerm/index.js.map +1 -0
- package/dist/validation.cjs +238 -0
- package/dist/validation.cjs.map +1 -0
- package/dist/validation.d.cts +2 -0
- package/dist/validation.d.ts +2 -44
- package/dist/validation.js +11 -246
- package/dist/validation.js.map +1 -0
- package/package.json +25 -30
- package/dist/components/GlossaryPage.test.js +0 -205
- package/dist/index.d.ts.map +0 -1
- package/dist/preset.d.ts.map +0 -1
- package/dist/remark/glossary-terms.d.ts +0 -28
- package/dist/remark/glossary-terms.d.ts.map +0 -1
- package/dist/theme/GlossaryTerm/index.test.js +0 -143
- package/dist/validation.d.ts.map +0 -1
- /package/dist/{components/GlossaryPage.module.css → GlossaryPage.module-M4DEUP4X.module.css} +0 -0
- /package/dist/{theme/GlossaryTerm/styles.module.css → styles.module-N7ME3MWS.module.css} +0 -0
|
@@ -1,440 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* @param {string} filePath - Path to the file (for error messages)
|
|
11
|
-
* @returns {{ terms: Array<{term: string, definition: string}>, errors: string[] }}
|
|
12
|
-
*/
|
|
13
|
-
function validateGlossaryTerms(data, _filePath) {
|
|
14
|
-
const errors = [];
|
|
15
|
-
const validTerms = [];
|
|
16
|
-
|
|
17
|
-
if (data === null || data === undefined) {
|
|
18
|
-
errors.push(`Glossary data is null or undefined`);
|
|
19
|
-
return { terms: [], errors };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (typeof data !== 'object') {
|
|
23
|
-
errors.push(`Glossary data must be an object, got ${typeof data}`);
|
|
24
|
-
return { terms: [], errors };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!('terms' in data)) {
|
|
28
|
-
errors.push(`Glossary data must contain a "terms" array`);
|
|
29
|
-
return { terms: [], errors };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!Array.isArray(data.terms)) {
|
|
33
|
-
errors.push(`Field "terms" must be an array, got ${typeof data.terms}`);
|
|
34
|
-
return { terms: [], errors };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
data.terms.forEach((term, index) => {
|
|
38
|
-
if (term === null || term === undefined || typeof term !== 'object') {
|
|
39
|
-
errors.push(`terms[${index}]: Term must be an object`);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (typeof term.term !== 'string' || term.term.trim() === '') {
|
|
44
|
-
errors.push(`terms[${index}]: Missing or invalid "term" field`);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (typeof term.definition !== 'string') {
|
|
49
|
-
errors.push(`terms[${index}]: Missing or invalid "definition" field`);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
validTerms.push(term);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
return { terms: validTerms, errors };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Cache for glossary data to avoid repeated synchronous file reads
|
|
60
|
-
// Key: absolute file path, Value: { terms, loadedAt }
|
|
61
|
-
const glossaryCache = new Map();
|
|
62
|
-
const CACHE_TTL = 5000; // 5 seconds TTL to allow for file changes during dev
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Creates a remark plugin that automatically detects and replaces glossary terms in markdown
|
|
66
|
-
*
|
|
67
|
-
* This plugin transforms plain text terms into <GlossaryTerm> JSX elements.
|
|
68
|
-
* The GlossaryTerm component is globally available via the MDXComponents theme wrapper,
|
|
69
|
-
* so no import injection is needed - MDX files can use it without explicit imports.
|
|
70
|
-
*
|
|
71
|
-
* @param {object} options - Plugin options
|
|
72
|
-
* @param {Array} options.terms - Array of glossary term objects with {term, definition}
|
|
73
|
-
* @param {string} options.glossaryPath - Path to glossary JSON file (optional, if terms not provided)
|
|
74
|
-
* @param {string} options.routePath - Route path to glossary page (default: '/glossary')
|
|
75
|
-
* @param {string} options.siteDir - Docusaurus site directory (required if using glossaryPath)
|
|
76
|
-
* @returns {function} Remark plugin function
|
|
77
|
-
*/
|
|
78
|
-
export default function remarkGlossaryTerms({
|
|
79
|
-
terms = [],
|
|
80
|
-
glossaryPath = null,
|
|
81
|
-
routePath = '/glossary',
|
|
82
|
-
siteDir = null,
|
|
83
|
-
} = {}) {
|
|
84
|
-
let glossaryTerms = terms;
|
|
85
|
-
|
|
86
|
-
// If terms not provided, try to load from glossaryPath with caching
|
|
87
|
-
if (!glossaryTerms.length && glossaryPath && siteDir) {
|
|
88
|
-
try {
|
|
89
|
-
const glossaryFilePath = path.resolve(siteDir, glossaryPath);
|
|
90
|
-
const now = Date.now();
|
|
91
|
-
|
|
92
|
-
// Check cache first to avoid repeated file reads
|
|
93
|
-
const cached = glossaryCache.get(glossaryFilePath);
|
|
94
|
-
if (cached && now - cached.loadedAt < CACHE_TTL) {
|
|
95
|
-
glossaryTerms = cached.terms;
|
|
96
|
-
} else {
|
|
97
|
-
// Cache miss or expired - load from file synchronously
|
|
98
|
-
// Note: This is synchronous I/O which can block the build process
|
|
99
|
-
// Consider passing terms directly to avoid this
|
|
100
|
-
if (fs.existsSync(glossaryFilePath)) {
|
|
101
|
-
const fileContent = fs.readFileSync(glossaryFilePath, 'utf8');
|
|
102
|
-
let glossaryData;
|
|
103
|
-
try {
|
|
104
|
-
glossaryData = JSON.parse(fileContent);
|
|
105
|
-
} catch (parseError) {
|
|
106
|
-
console.error(
|
|
107
|
-
`[glossary-plugin] Failed to parse glossary JSON at ${glossaryPath}:`,
|
|
108
|
-
parseError.message
|
|
109
|
-
);
|
|
110
|
-
glossaryCache.set(glossaryFilePath, {
|
|
111
|
-
terms: [],
|
|
112
|
-
loadedAt: now,
|
|
113
|
-
});
|
|
114
|
-
return tree => tree;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Validate glossary data
|
|
118
|
-
const { terms: validTerms, errors } = validateGlossaryTerms(glossaryData, glossaryPath);
|
|
119
|
-
|
|
120
|
-
if (errors.length > 0) {
|
|
121
|
-
console.warn(`[glossary-plugin] Glossary validation errors in ${glossaryPath}:`);
|
|
122
|
-
errors.forEach(err => console.warn(` - ${err}`));
|
|
123
|
-
if (validTerms.length > 0) {
|
|
124
|
-
console.warn(`[glossary-plugin] Proceeding with ${validTerms.length} valid term(s).`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
glossaryTerms = validTerms;
|
|
129
|
-
|
|
130
|
-
// Update cache
|
|
131
|
-
glossaryCache.set(glossaryFilePath, {
|
|
132
|
-
terms: glossaryTerms,
|
|
133
|
-
loadedAt: now,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// Log only once per file (when cache is first populated)
|
|
137
|
-
if (!cached && process.env.NODE_ENV !== 'production') {
|
|
138
|
-
console.log(
|
|
139
|
-
`[glossary-plugin] Loaded ${glossaryTerms.length} terms from ${glossaryPath}`
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
// File doesn't exist - cache empty result to avoid repeated checks
|
|
144
|
-
glossaryCache.set(glossaryFilePath, {
|
|
145
|
-
terms: [],
|
|
146
|
-
loadedAt: now,
|
|
147
|
-
});
|
|
148
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
149
|
-
console.warn(`[glossary-plugin] Glossary file not found: ${glossaryPath}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.warn(
|
|
155
|
-
`[glossary-plugin] Failed to load glossary from ${glossaryPath}:`,
|
|
156
|
-
error.message
|
|
157
|
-
);
|
|
158
|
-
// Cache the error to avoid repeated attempts
|
|
159
|
-
if (glossaryPath && siteDir) {
|
|
160
|
-
const glossaryFilePath = path.resolve(siteDir, glossaryPath);
|
|
161
|
-
glossaryCache.set(glossaryFilePath, {
|
|
162
|
-
terms: [],
|
|
163
|
-
loadedAt: Date.now(),
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Build a map of terms for efficient lookup
|
|
170
|
-
// Key: lowercase term, Value: term object with original case
|
|
171
|
-
const termMap = new Map();
|
|
172
|
-
glossaryTerms.forEach(termObj => {
|
|
173
|
-
if (termObj.term) {
|
|
174
|
-
termMap.set(termObj.term.toLowerCase(), termObj);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// Sort terms by length (longest first) to avoid partial matches
|
|
179
|
-
// e.g., "Application Programming Interface" should match before "API"
|
|
180
|
-
const sortedTerms = Array.from(termMap.entries()).sort((a, b) => b[0].length - a[0].length);
|
|
181
|
-
|
|
182
|
-
// If no terms, return a no-op transformer
|
|
183
|
-
if (sortedTerms.length === 0) {
|
|
184
|
-
return tree => tree;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Recursively replace glossary terms in text
|
|
189
|
-
* Returns an array of text nodes and MDX components
|
|
190
|
-
*/
|
|
191
|
-
function replaceTermsInText(text) {
|
|
192
|
-
if (!text || !sortedTerms.length) {
|
|
193
|
-
return [{ type: 'text', value: text }];
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const result = [];
|
|
197
|
-
let lastIndex = 0;
|
|
198
|
-
const textLower = text.toLowerCase();
|
|
199
|
-
|
|
200
|
-
// Find all matches
|
|
201
|
-
const matches = [];
|
|
202
|
-
for (const [lowerTerm, termObj] of sortedTerms) {
|
|
203
|
-
const term = termObj.term;
|
|
204
|
-
let searchIndex = 0;
|
|
205
|
-
|
|
206
|
-
while (searchIndex < textLower.length) {
|
|
207
|
-
const index = textLower.indexOf(lowerTerm, searchIndex);
|
|
208
|
-
if (index === -1) break;
|
|
209
|
-
|
|
210
|
-
// Check if it's a whole word match, with simple plural tolerance ('s' or 'es')
|
|
211
|
-
const beforeChar = index > 0 ? textLower[index - 1] : ' ';
|
|
212
|
-
const afterIndex = index + lowerTerm.length;
|
|
213
|
-
const afterChar = afterIndex < textLower.length ? textLower[afterIndex] : ' ';
|
|
214
|
-
|
|
215
|
-
let matchLength = term.length;
|
|
216
|
-
let isWordBoundary = !/\w/.test(beforeChar) && !/\w/.test(afterChar);
|
|
217
|
-
|
|
218
|
-
// Allow trailing 's' plural (e.g., webhook -> webhooks)
|
|
219
|
-
if (!isWordBoundary && afterChar === 's') {
|
|
220
|
-
const nextChar = afterIndex + 1 < textLower.length ? textLower[afterIndex + 1] : ' ';
|
|
221
|
-
if (!/\w/.test(nextChar)) {
|
|
222
|
-
isWordBoundary = true;
|
|
223
|
-
matchLength = term.length + 1;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Allow trailing 'es' plural (e.g., API -> APIs, box -> boxes)
|
|
228
|
-
if (
|
|
229
|
-
!isWordBoundary &&
|
|
230
|
-
afterChar === 'e' &&
|
|
231
|
-
afterIndex + 1 < textLower.length &&
|
|
232
|
-
textLower[afterIndex + 1] === 's'
|
|
233
|
-
) {
|
|
234
|
-
const nextChar = afterIndex + 2 < textLower.length ? textLower[afterIndex + 2] : ' ';
|
|
235
|
-
if (!/\w/.test(nextChar)) {
|
|
236
|
-
isWordBoundary = true;
|
|
237
|
-
matchLength = term.length + 2;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (isWordBoundary) {
|
|
242
|
-
matches.push({
|
|
243
|
-
index,
|
|
244
|
-
length: matchLength,
|
|
245
|
-
term: term,
|
|
246
|
-
termObj: termObj,
|
|
247
|
-
// Store original case from the text
|
|
248
|
-
originalText: text.substring(index, index + matchLength),
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
searchIndex = index + 1;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Sort matches by index
|
|
257
|
-
matches.sort((a, b) => a.index - b.index);
|
|
258
|
-
|
|
259
|
-
// Remove overlapping matches (keep the first one)
|
|
260
|
-
const nonOverlappingMatches = [];
|
|
261
|
-
let lastMatchEnd = 0;
|
|
262
|
-
for (const match of matches) {
|
|
263
|
-
if (match.index >= lastMatchEnd) {
|
|
264
|
-
nonOverlappingMatches.push(match);
|
|
265
|
-
lastMatchEnd = match.index + match.length;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Build result array
|
|
270
|
-
for (const match of nonOverlappingMatches) {
|
|
271
|
-
// Add text before match
|
|
272
|
-
if (match.index > lastIndex) {
|
|
273
|
-
result.push({
|
|
274
|
-
type: 'text',
|
|
275
|
-
value: text.substring(lastIndex, match.index),
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Add MDX component for glossary term
|
|
280
|
-
result.push({
|
|
281
|
-
type: 'mdxJsxFlowElement',
|
|
282
|
-
name: 'GlossaryTerm',
|
|
283
|
-
attributes: [
|
|
284
|
-
{
|
|
285
|
-
type: 'mdxJsxAttribute',
|
|
286
|
-
name: 'term',
|
|
287
|
-
value: match.termObj.term,
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
type: 'mdxJsxAttribute',
|
|
291
|
-
name: 'definition',
|
|
292
|
-
value: match.termObj.definition || '',
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
type: 'mdxJsxAttribute',
|
|
296
|
-
name: 'routePath',
|
|
297
|
-
value: routePath,
|
|
298
|
-
},
|
|
299
|
-
],
|
|
300
|
-
children: [
|
|
301
|
-
{
|
|
302
|
-
type: 'text',
|
|
303
|
-
value: match.originalText,
|
|
304
|
-
},
|
|
305
|
-
],
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
lastIndex = match.index + match.length;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Add remaining text
|
|
312
|
-
if (lastIndex < text.length) {
|
|
313
|
-
result.push({
|
|
314
|
-
type: 'text',
|
|
315
|
-
value: text.substring(lastIndex),
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return result.length > 0 ? result : [{ type: 'text', value: text }];
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Return the transformer function
|
|
323
|
-
const transformer = tree => {
|
|
324
|
-
let usedGlossaryTerm = false;
|
|
325
|
-
visit(tree, 'text', (node, index, parent) => {
|
|
326
|
-
// Skip text nodes inside code blocks, links, or existing MDX components
|
|
327
|
-
if (
|
|
328
|
-
parent.type === 'code' ||
|
|
329
|
-
parent.type === 'inlineCode' ||
|
|
330
|
-
parent.type === 'link' ||
|
|
331
|
-
parent.type === 'mdxJsxFlowElement' ||
|
|
332
|
-
parent.type === 'mdxJsxTextElement'
|
|
333
|
-
) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Replace terms in text node
|
|
338
|
-
const replacements = replaceTermsInText(node.value);
|
|
339
|
-
|
|
340
|
-
// If we have replacements, replace the single text node with multiple nodes
|
|
341
|
-
if (
|
|
342
|
-
replacements.length > 1 ||
|
|
343
|
-
(replacements.length === 1 && replacements[0].type !== 'text')
|
|
344
|
-
) {
|
|
345
|
-
// Convert to text elements for paragraph context if needed
|
|
346
|
-
const newNodes = replacements.map(replacement => {
|
|
347
|
-
if (replacement.type === 'mdxJsxFlowElement') {
|
|
348
|
-
// In paragraph context, we need mdxJsxTextElement instead
|
|
349
|
-
if (parent.type === 'paragraph') {
|
|
350
|
-
usedGlossaryTerm = true;
|
|
351
|
-
return {
|
|
352
|
-
type: 'mdxJsxTextElement',
|
|
353
|
-
name: replacement.name,
|
|
354
|
-
attributes: replacement.attributes,
|
|
355
|
-
children: replacement.children,
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
usedGlossaryTerm = true;
|
|
359
|
-
}
|
|
360
|
-
return replacement;
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// Replace the single node with multiple nodes
|
|
364
|
-
parent.children.splice(index, 1, ...newNodes);
|
|
365
|
-
return index + newNodes.length - 1; // Return new index to continue
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
// Inject MDX import for GlossaryTerm if we used it
|
|
370
|
-
// The component is available via theme path, so we just need to import it
|
|
371
|
-
if (usedGlossaryTerm) {
|
|
372
|
-
const importNode = {
|
|
373
|
-
type: 'mdxjsEsm',
|
|
374
|
-
value: 'import GlossaryTerm from "@theme/GlossaryTerm";',
|
|
375
|
-
data: {
|
|
376
|
-
estree: {
|
|
377
|
-
type: 'Program',
|
|
378
|
-
sourceType: 'module',
|
|
379
|
-
body: [
|
|
380
|
-
{
|
|
381
|
-
type: 'ImportDeclaration',
|
|
382
|
-
specifiers: [
|
|
383
|
-
{
|
|
384
|
-
type: 'ImportDefaultSpecifier',
|
|
385
|
-
local: { type: 'Identifier', name: 'GlossaryTerm' },
|
|
386
|
-
},
|
|
387
|
-
],
|
|
388
|
-
source: {
|
|
389
|
-
type: 'Literal',
|
|
390
|
-
value: '@theme/GlossaryTerm',
|
|
391
|
-
raw: '"@theme/GlossaryTerm"',
|
|
392
|
-
},
|
|
393
|
-
},
|
|
394
|
-
],
|
|
395
|
-
},
|
|
396
|
-
},
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
// Check for existing import
|
|
400
|
-
const hasImport =
|
|
401
|
-
Array.isArray(tree.children) &&
|
|
402
|
-
tree.children.some(
|
|
403
|
-
n =>
|
|
404
|
-
n.type === 'mdxjsEsm' &&
|
|
405
|
-
(n.value?.includes('@theme/GlossaryTerm') ||
|
|
406
|
-
n.data?.estree?.body?.some(s => s.source?.value === '@theme/GlossaryTerm'))
|
|
407
|
-
);
|
|
408
|
-
|
|
409
|
-
if (!hasImport) {
|
|
410
|
-
if (!Array.isArray(tree.children)) tree.children = [];
|
|
411
|
-
let insertIndex = 0;
|
|
412
|
-
for (let i = 0; i < tree.children.length; i++) {
|
|
413
|
-
const node = tree.children[i];
|
|
414
|
-
if (node.type === 'yaml' || node.type === 'toml') {
|
|
415
|
-
insertIndex = i + 1;
|
|
416
|
-
} else {
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
tree.children.splice(insertIndex, 0, importNode);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
return transformer;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Clears the glossary cache
|
|
430
|
-
* Useful for testing or when you want to force a reload of glossary data
|
|
431
|
-
*
|
|
432
|
-
* @param {string} [filePath] - Optional specific file path to clear. If not provided, clears entire cache.
|
|
433
|
-
*/
|
|
434
|
-
export function clearGlossaryCache(filePath) {
|
|
435
|
-
if (filePath) {
|
|
436
|
-
glossaryCache.delete(filePath);
|
|
437
|
-
} else {
|
|
438
|
-
glossaryCache.clear();
|
|
439
|
-
}
|
|
440
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
clearGlossaryCache,
|
|
3
|
+
remarkGlossaryTerms
|
|
4
|
+
} from "../chunk-PEB4Y6RI.js";
|
|
5
|
+
export {
|
|
6
|
+
clearGlossaryCache,
|
|
7
|
+
remarkGlossaryTerms as default
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=glossary-terms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/theme/GlossaryTerm/index.js
|
|
31
|
+
var GlossaryTerm_exports = {};
|
|
32
|
+
__export(GlossaryTerm_exports, {
|
|
33
|
+
default: () => GlossaryTerm
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(GlossaryTerm_exports);
|
|
36
|
+
var import_react = __toESM(require("react"), 1);
|
|
37
|
+
var import_useGlobalData = require("@docusaurus/useGlobalData");
|
|
38
|
+
var import_styles = __toESM(require("../../styles.module-N7ME3MWS.module.css"), 1);
|
|
39
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
40
|
+
function GlossaryTerm({ term, definition, routePath = "/glossary", children }) {
|
|
41
|
+
const [showTooltip, setShowTooltip] = (0, import_react.useState)(false);
|
|
42
|
+
const [tooltipStyle, setTooltipStyle] = (0, import_react.useState)(null);
|
|
43
|
+
const [placement, setPlacement] = (0, import_react.useState)("top");
|
|
44
|
+
const wrapperRef = (0, import_react.useRef)(null);
|
|
45
|
+
const tooltipRef = (0, import_react.useRef)(null);
|
|
46
|
+
const updatePosition = (0, import_react.useCallback)(() => {
|
|
47
|
+
if (!wrapperRef.current || !tooltipRef.current) return;
|
|
48
|
+
const wrapperRect = wrapperRef.current.getBoundingClientRect();
|
|
49
|
+
const tooltipRect = tooltipRef.current.getBoundingClientRect();
|
|
50
|
+
const viewportWidth = window.innerWidth;
|
|
51
|
+
const viewportHeight = window.innerHeight;
|
|
52
|
+
const preferredGap = 8;
|
|
53
|
+
const hasSpaceAbove = wrapperRect.top >= tooltipRect.height + preferredGap;
|
|
54
|
+
const hasSpaceBelow = viewportHeight - wrapperRect.bottom >= tooltipRect.height + preferredGap;
|
|
55
|
+
const nextPlacement = hasSpaceAbove || !hasSpaceBelow ? "top" : "bottom";
|
|
56
|
+
let top;
|
|
57
|
+
if (nextPlacement === "top") {
|
|
58
|
+
top = wrapperRect.top - tooltipRect.height - preferredGap;
|
|
59
|
+
} else {
|
|
60
|
+
top = wrapperRect.bottom + preferredGap;
|
|
61
|
+
}
|
|
62
|
+
const horizontalMargin = 8;
|
|
63
|
+
let left = wrapperRect.left + wrapperRect.width / 2 - tooltipRect.width / 2;
|
|
64
|
+
left = Math.max(
|
|
65
|
+
horizontalMargin,
|
|
66
|
+
Math.min(left, viewportWidth - tooltipRect.width - horizontalMargin)
|
|
67
|
+
);
|
|
68
|
+
setPlacement(nextPlacement);
|
|
69
|
+
setTooltipStyle({ top: Math.max(4, top), left });
|
|
70
|
+
}, []);
|
|
71
|
+
(0, import_react.useEffect)(() => {
|
|
72
|
+
if (!showTooltip) return;
|
|
73
|
+
let rafId2;
|
|
74
|
+
const rafId1 = requestAnimationFrame(() => {
|
|
75
|
+
rafId2 = requestAnimationFrame(() => {
|
|
76
|
+
updatePosition();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
const onScroll = () => updatePosition();
|
|
80
|
+
const onResize = () => updatePosition();
|
|
81
|
+
window.addEventListener("scroll", onScroll, true);
|
|
82
|
+
window.addEventListener("resize", onResize);
|
|
83
|
+
return () => {
|
|
84
|
+
cancelAnimationFrame(rafId1);
|
|
85
|
+
if (rafId2) cancelAnimationFrame(rafId2);
|
|
86
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
87
|
+
window.removeEventListener("resize", onResize);
|
|
88
|
+
};
|
|
89
|
+
}, [showTooltip, updatePosition]);
|
|
90
|
+
const pluginData = (0, import_useGlobalData.usePluginData)("docusaurus-plugin-glossary");
|
|
91
|
+
const effectiveDefinition = (0, import_react.useMemo)(() => {
|
|
92
|
+
if (definition && typeof definition === "string" && definition.length > 0) {
|
|
93
|
+
return definition;
|
|
94
|
+
}
|
|
95
|
+
const terms = pluginData && pluginData.terms || [];
|
|
96
|
+
const found = terms.find(
|
|
97
|
+
(t) => typeof t.term === "string" && t.term.toLowerCase() === String(term).toLowerCase()
|
|
98
|
+
);
|
|
99
|
+
return found && found.definition ? found.definition : void 0;
|
|
100
|
+
}, [definition, pluginData, term]);
|
|
101
|
+
const effectiveRoutePath = (0, import_react.useMemo)(() => {
|
|
102
|
+
if (routePath && typeof routePath === "string" && routePath.length > 0) return routePath;
|
|
103
|
+
return pluginData && pluginData.routePath || "/glossary";
|
|
104
|
+
}, [pluginData, routePath]);
|
|
105
|
+
const displayText = children || term;
|
|
106
|
+
const termId = term.toLowerCase().replace(/\s+/g, "-");
|
|
107
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { ref: wrapperRef, className: import_styles.default.glossaryTermWrapper, children: [
|
|
108
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
109
|
+
"a",
|
|
110
|
+
{
|
|
111
|
+
href: `${effectiveRoutePath}#${termId}`,
|
|
112
|
+
className: import_styles.default.glossaryTerm,
|
|
113
|
+
onMouseEnter: () => setShowTooltip(true),
|
|
114
|
+
onMouseLeave: () => setShowTooltip(false),
|
|
115
|
+
onFocus: () => setShowTooltip(true),
|
|
116
|
+
onBlur: () => setShowTooltip(false),
|
|
117
|
+
"aria-describedby": `tooltip-${termId}`,
|
|
118
|
+
children: displayText
|
|
119
|
+
}
|
|
120
|
+
),
|
|
121
|
+
effectiveDefinition && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
122
|
+
"span",
|
|
123
|
+
{
|
|
124
|
+
ref: tooltipRef,
|
|
125
|
+
id: `tooltip-${termId}`,
|
|
126
|
+
className: `${import_styles.default.tooltip} ${showTooltip ? import_styles.default.tooltipVisible : ""} ${placement === "top" ? import_styles.default.tooltipTop : import_styles.default.tooltipBottom} ${import_styles.default.tooltipFloating}`,
|
|
127
|
+
role: "tooltip",
|
|
128
|
+
style: showTooltip && tooltipStyle ? { top: `${tooltipStyle.top}px`, left: `${tooltipStyle.left}px` } : void 0,
|
|
129
|
+
children: [
|
|
130
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: term }),
|
|
131
|
+
" ",
|
|
132
|
+
effectiveDefinition
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
] });
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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({ term, definition, routePath = '/glossary', children }) {\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 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> {effectiveDefinition}\n </span>\n )}\n </span>\n );\n}\n"],"mappings":"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","names":["styles"]}
|