docusaurus-plugin-glossary 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -25
- package/index.js +49 -2
- package/package.json +4 -2
- package/remark/glossary-terms.js +214 -0
- package/theme/GlossaryTerm/index.js +3 -2
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ A comprehensive Docusaurus plugin that provides glossary functionality with an a
|
|
|
7
7
|
- **Auto-generated Glossary Page**: Displays all terms alphabetically with letter navigation
|
|
8
8
|
- **Search Functionality**: Real-time search across terms and definitions
|
|
9
9
|
- **GlossaryTerm Component**: Inline component for linking terms with tooltip previews
|
|
10
|
+
- **Automatic Term Detection**: Automatically detect and link glossary terms in markdown files with tooltips
|
|
10
11
|
- **Responsive Design**: Mobile-friendly UI with dark mode support
|
|
11
12
|
- **Related Terms**: Link between related glossary terms
|
|
12
13
|
- **Abbreviation Support**: Display full form of abbreviated terms
|
|
@@ -24,15 +25,44 @@ A comprehensive Docusaurus plugin that provides glossary functionality with an a
|
|
|
24
25
|
|
|
25
26
|
2. Add the plugin to your `docusaurus.config.js`:
|
|
26
27
|
```javascript
|
|
27
|
-
plugins
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
const glossaryPlugin = require.resolve('./src/plugins/docusaurus-plugin-glossary');
|
|
29
|
+
|
|
30
|
+
module.exports = {
|
|
31
|
+
// ... other config
|
|
32
|
+
plugins: [
|
|
33
|
+
[
|
|
34
|
+
glossaryPlugin,
|
|
35
|
+
{
|
|
36
|
+
glossaryPath: 'glossary/glossary.json', // optional, default: 'glossary/glossary.json'
|
|
37
|
+
routePath: '/glossary', // optional, default: '/glossary'
|
|
38
|
+
autoLinkTerms: true, // optional, default: true - automatically link terms in markdown
|
|
39
|
+
},
|
|
40
|
+
],
|
|
34
41
|
],
|
|
35
|
-
|
|
42
|
+
// ... other config
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. **Enable automatic term detection** by adding the remark plugin to your markdown configuration:
|
|
47
|
+
```javascript
|
|
48
|
+
const glossaryPlugin = require('./src/plugins/docusaurus-plugin-glossary');
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
// ... other config
|
|
52
|
+
markdown: {
|
|
53
|
+
remarkPlugins: [
|
|
54
|
+
[
|
|
55
|
+
glossaryPlugin.remarkPlugin,
|
|
56
|
+
{
|
|
57
|
+
glossaryPath: 'glossary/glossary.json',
|
|
58
|
+
routePath: '/glossary',
|
|
59
|
+
siteDir: process.cwd(), // or your site directory
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
// ... other config
|
|
65
|
+
};
|
|
36
66
|
```
|
|
37
67
|
|
|
38
68
|
### For Separate Package (To Publish)
|
|
@@ -54,6 +84,8 @@ To publish this as a separate npm package:
|
|
|
54
84
|
├── components/
|
|
55
85
|
│ ├── GlossaryPage.js
|
|
56
86
|
│ └── GlossaryPage.module.css
|
|
87
|
+
├── remark/
|
|
88
|
+
│ └── glossary-terms.js
|
|
57
89
|
├── theme/
|
|
58
90
|
│ └── GlossaryTerm/
|
|
59
91
|
│ ├── index.js
|
|
@@ -70,6 +102,14 @@ To publish this as a separate npm package:
|
|
|
70
102
|
"version": "1.0.0",
|
|
71
103
|
"description": "A Docusaurus plugin for creating and managing glossary terms",
|
|
72
104
|
"main": "index.js",
|
|
105
|
+
"files": [
|
|
106
|
+
"index.js",
|
|
107
|
+
"components/",
|
|
108
|
+
"theme/",
|
|
109
|
+
"remark/",
|
|
110
|
+
"README.md",
|
|
111
|
+
"LICENSE"
|
|
112
|
+
],
|
|
73
113
|
"keywords": ["docusaurus", "glossary", "plugin", "documentation"],
|
|
74
114
|
"peerDependencies": {
|
|
75
115
|
"@docusaurus/core": "^3.0.0",
|
|
@@ -77,7 +117,11 @@ To publish this as a separate npm package:
|
|
|
77
117
|
"react-dom": "^18.0.0"
|
|
78
118
|
},
|
|
79
119
|
"dependencies": {
|
|
80
|
-
"fs-extra": "^11.0.0"
|
|
120
|
+
"fs-extra": "^11.0.0",
|
|
121
|
+
"unist-util-visit": "^5.0.0"
|
|
122
|
+
},
|
|
123
|
+
"engines": {
|
|
124
|
+
"node": ">=16.14"
|
|
81
125
|
}
|
|
82
126
|
}
|
|
83
127
|
```
|
|
@@ -96,15 +140,43 @@ To publish this as a separate npm package:
|
|
|
96
140
|
|
|
97
141
|
6. Add to your `docusaurus.config.js`:
|
|
98
142
|
```javascript
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
143
|
+
const glossaryPlugin = require('docusaurus-plugin-glossary');
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
// ... other config
|
|
147
|
+
plugins: [
|
|
148
|
+
[
|
|
149
|
+
'docusaurus-plugin-glossary',
|
|
150
|
+
{
|
|
151
|
+
glossaryPath: 'glossary/glossary.json',
|
|
152
|
+
routePath: '/glossary',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
106
155
|
],
|
|
107
|
-
|
|
156
|
+
// ... other config
|
|
157
|
+
};
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
7. **Enable automatic term detection** by adding the remark plugin to your markdown configuration:
|
|
161
|
+
```javascript
|
|
162
|
+
const glossaryPlugin = require('docusaurus-plugin-glossary');
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
// ... other config
|
|
166
|
+
markdown: {
|
|
167
|
+
remarkPlugins: [
|
|
168
|
+
[
|
|
169
|
+
glossaryPlugin.remarkPlugin,
|
|
170
|
+
{
|
|
171
|
+
glossaryPath: 'glossary/glossary.json',
|
|
172
|
+
routePath: '/glossary',
|
|
173
|
+
siteDir: process.cwd(), // or your site directory
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
// ... other config
|
|
179
|
+
};
|
|
108
180
|
```
|
|
109
181
|
|
|
110
182
|
## Usage
|
|
@@ -143,9 +215,26 @@ Each term object can include:
|
|
|
143
215
|
- `relatedTerms` (optional): Array of related term names
|
|
144
216
|
- `id` (optional): Custom ID for linking (auto-generated from term if not provided)
|
|
145
217
|
|
|
146
|
-
### 3.
|
|
218
|
+
### 3. Automatic Term Detection
|
|
219
|
+
|
|
220
|
+
When the remark plugin is configured (see Installation), glossary terms are automatically detected and linked in all markdown files. Simply write your content normally:
|
|
221
|
+
|
|
222
|
+
```markdown
|
|
223
|
+
Our API uses REST principles to provide a simple interface.
|
|
224
|
+
|
|
225
|
+
This project supports webhooks for real-time notifications.
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Terms like "API", "REST", and "webhooks" will automatically be detected if they're defined in your glossary and will appear with:
|
|
229
|
+
- Dotted underline styling
|
|
230
|
+
- Tooltip showing definition on hover
|
|
231
|
+
- Link to full glossary page entry
|
|
232
|
+
|
|
233
|
+
**Note**: Automatic detection works for whole words only and respects word boundaries. Terms inside code blocks, links, or existing MDX components are not processed.
|
|
234
|
+
|
|
235
|
+
### 4. Using the GlossaryTerm Component Manually
|
|
147
236
|
|
|
148
|
-
|
|
237
|
+
For more control or when automatic detection isn't sufficient, you can manually import and use the `GlossaryTerm` component in your MDX files:
|
|
149
238
|
|
|
150
239
|
```jsx
|
|
151
240
|
import GlossaryTerm from '@theme/GlossaryTerm';
|
|
@@ -163,7 +252,7 @@ The component features:
|
|
|
163
252
|
- Link to full glossary page entry
|
|
164
253
|
- Accessible with keyboard navigation
|
|
165
254
|
|
|
166
|
-
###
|
|
255
|
+
### 5. Accessing the Glossary Page
|
|
167
256
|
|
|
168
257
|
The glossary page is automatically available at `/glossary` (or your configured `routePath`).
|
|
169
258
|
|
|
@@ -177,10 +266,11 @@ Features:
|
|
|
177
266
|
|
|
178
267
|
## Configuration Options
|
|
179
268
|
|
|
180
|
-
| Option | Type
|
|
181
|
-
| -------------- |
|
|
182
|
-
| `glossaryPath` | string
|
|
183
|
-
| `routePath` | string
|
|
269
|
+
| Option | Type | Default | Description |
|
|
270
|
+
| -------------- | ------- | -------------------------- | ----------------------------------------------------- |
|
|
271
|
+
| `glossaryPath` | string | `'glossary/glossary.json'` | Path to glossary JSON file relative to site directory |
|
|
272
|
+
| `routePath` | string | `'/glossary'` | URL path for glossary page |
|
|
273
|
+
| `autoLinkTerms`| boolean | `true` | Enable automatic term detection in markdown (requires remark plugin configuration) |
|
|
184
274
|
|
|
185
275
|
## Customization
|
|
186
276
|
|
|
@@ -241,7 +331,24 @@ themeConfig: {
|
|
|
241
331
|
}
|
|
242
332
|
```
|
|
243
333
|
|
|
244
|
-
### Example 2:
|
|
334
|
+
### Example 2: Automatic Term Detection
|
|
335
|
+
|
|
336
|
+
With the remark plugin configured, you can simply write markdown normally:
|
|
337
|
+
|
|
338
|
+
```markdown
|
|
339
|
+
---
|
|
340
|
+
title: API Documentation
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
# Getting Started with Our API
|
|
344
|
+
|
|
345
|
+
Our API uses RESTful principles to provide a simple and consistent interface.
|
|
346
|
+
Webhooks are supported for real-time event notifications.
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The terms "API", "RESTful", and "Webhooks" will automatically be detected and linked if they're defined in your glossary.
|
|
350
|
+
|
|
351
|
+
### Example 3: Using in MDX Manually
|
|
245
352
|
|
|
246
353
|
```mdx
|
|
247
354
|
---
|
|
@@ -267,6 +374,8 @@ docusaurus-plugin-glossary/
|
|
|
267
374
|
├── components/
|
|
268
375
|
│ ├── GlossaryPage.js # Glossary page component
|
|
269
376
|
│ └── GlossaryPage.module.css # Glossary page styles
|
|
377
|
+
├── remark/
|
|
378
|
+
│ └── glossary-terms.js # Remark plugin for automatic term detection
|
|
270
379
|
├── theme/
|
|
271
380
|
│ └── GlossaryTerm/
|
|
272
381
|
│ ├── index.js # Term component
|
|
@@ -281,6 +390,15 @@ docusaurus-plugin-glossary/
|
|
|
281
390
|
3. **getThemePath**: Exposes theme components
|
|
282
391
|
4. **getPathsToWatch**: Watches glossary file for changes
|
|
283
392
|
|
|
393
|
+
### Remark Plugin
|
|
394
|
+
|
|
395
|
+
The remark plugin (`remark/glossary-terms.js`) automatically detects glossary terms in markdown files and replaces them with `GlossaryTerm` components. It:
|
|
396
|
+
|
|
397
|
+
- Scans text nodes for glossary terms (case-insensitive, whole word matching)
|
|
398
|
+
- Replaces matching terms with MDX components that show tooltips
|
|
399
|
+
- Skips terms inside code blocks, links, or existing MDX components
|
|
400
|
+
- Respects word boundaries to avoid partial matches
|
|
401
|
+
|
|
284
402
|
## Troubleshooting
|
|
285
403
|
|
|
286
404
|
### Glossary page returns 404
|
|
@@ -301,6 +419,14 @@ docusaurus-plugin-glossary/
|
|
|
301
419
|
- Try clearing cache with `npm run clear`
|
|
302
420
|
- Restart dev server
|
|
303
421
|
|
|
422
|
+
### Automatic term detection not working
|
|
423
|
+
|
|
424
|
+
- Ensure the remark plugin is configured in `markdown.remarkPlugins` in `docusaurus.config.js`
|
|
425
|
+
- Check that `glossaryPath` and `siteDir` are correctly configured in the remark plugin options
|
|
426
|
+
- Verify your glossary file exists and contains terms
|
|
427
|
+
- Try clearing cache with `npm run clear` and restarting the dev server
|
|
428
|
+
- Note that terms inside code blocks, links, or MDX components are not processed
|
|
429
|
+
|
|
304
430
|
### Styles not applying
|
|
305
431
|
|
|
306
432
|
- Check for CSS conflicts in your custom CSS
|
package/index.js
CHANGED
|
@@ -9,15 +9,23 @@ const fs = require('fs-extra');
|
|
|
9
9
|
* - Auto-generated glossary page
|
|
10
10
|
* - GlossaryTerm component for inline definitions
|
|
11
11
|
* - Tooltips on hover
|
|
12
|
+
* - Automatic glossary term detection in markdown files
|
|
12
13
|
*
|
|
13
14
|
* @param {object} context - Docusaurus context
|
|
14
15
|
* @param {object} options - Plugin options
|
|
15
16
|
* @param {string} options.glossaryPath - Path to glossary JSON file (default: 'glossary/glossary.json')
|
|
16
17
|
* @param {string} options.routePath - Route path for glossary page (default: '/glossary')
|
|
18
|
+
* @param {boolean} options.autoLinkTerms - Automatically link glossary terms in markdown (default: true)
|
|
17
19
|
* @returns {object} Plugin object
|
|
18
20
|
*/
|
|
19
21
|
function glossaryPlugin(context, options = {}) {
|
|
20
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
glossaryPath = 'glossary/glossary.json',
|
|
24
|
+
routePath = '/glossary',
|
|
25
|
+
autoLinkTerms = true
|
|
26
|
+
} = options;
|
|
27
|
+
|
|
28
|
+
let glossaryDataCache = { terms: [] };
|
|
21
29
|
|
|
22
30
|
return {
|
|
23
31
|
name: 'docusaurus-plugin-glossary',
|
|
@@ -28,10 +36,12 @@ function glossaryPlugin(context, options = {}) {
|
|
|
28
36
|
|
|
29
37
|
if (await fs.pathExists(glossaryFilePath)) {
|
|
30
38
|
const glossaryData = await fs.readJson(glossaryFilePath);
|
|
39
|
+
glossaryDataCache = glossaryData;
|
|
31
40
|
return glossaryData;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
console.warn(`Glossary file not found at ${glossaryFilePath}. Using empty glossary.`);
|
|
44
|
+
glossaryDataCache = { terms: [] };
|
|
35
45
|
return { terms: [] };
|
|
36
46
|
},
|
|
37
47
|
|
|
@@ -40,11 +50,20 @@ function glossaryPlugin(context, options = {}) {
|
|
|
40
50
|
|
|
41
51
|
// Create data file that can be imported by components
|
|
42
52
|
const glossaryDataPath = await createData('glossary-data.json', JSON.stringify(content));
|
|
53
|
+
|
|
54
|
+
// Create a data file for the remark plugin to access glossary terms
|
|
55
|
+
const remarkGlossaryDataPath = await createData(
|
|
56
|
+
'remark-glossary-data.json',
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
terms: content.terms || [],
|
|
59
|
+
routePath: routePath,
|
|
60
|
+
})
|
|
61
|
+
);
|
|
43
62
|
|
|
44
63
|
// Add glossary page route
|
|
45
64
|
addRoute({
|
|
46
65
|
path: routePath,
|
|
47
|
-
component: '
|
|
66
|
+
component: path.join(__dirname, 'components/GlossaryPage.js'),
|
|
48
67
|
exact: true,
|
|
49
68
|
modules: {
|
|
50
69
|
glossaryData: glossaryDataPath,
|
|
@@ -67,4 +86,32 @@ function glossaryPlugin(context, options = {}) {
|
|
|
67
86
|
};
|
|
68
87
|
}
|
|
69
88
|
|
|
89
|
+
// Export remark plugin factory for use in markdown configuration
|
|
90
|
+
glossaryPlugin.remarkPlugin = require('./remark/glossary-terms');
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Helper function to get the configured remark plugin
|
|
94
|
+
* This can be used in docusaurus.config.js markdown configuration
|
|
95
|
+
*
|
|
96
|
+
* @param {object} pluginOptions - Plugin options from docusaurus.config.js
|
|
97
|
+
* @param {object} context - Docusaurus context
|
|
98
|
+
* @returns {function} Configured remark plugin
|
|
99
|
+
*/
|
|
100
|
+
glossaryPlugin.getRemarkPlugin = function(pluginOptions, context) {
|
|
101
|
+
const {
|
|
102
|
+
glossaryPath = 'glossary/glossary.json',
|
|
103
|
+
routePath = '/glossary',
|
|
104
|
+
siteDir = context.siteDir
|
|
105
|
+
} = pluginOptions;
|
|
106
|
+
|
|
107
|
+
return [
|
|
108
|
+
require('./remark/glossary-terms'),
|
|
109
|
+
{
|
|
110
|
+
glossaryPath,
|
|
111
|
+
routePath,
|
|
112
|
+
siteDir,
|
|
113
|
+
}
|
|
114
|
+
];
|
|
115
|
+
};
|
|
116
|
+
|
|
70
117
|
module.exports = glossaryPlugin;
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docusaurus-plugin-glossary",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A Docusaurus plugin for creating and managing glossary terms with auto-generated pages and inline tooltips",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"index.js",
|
|
8
8
|
"components/",
|
|
9
9
|
"theme/",
|
|
10
|
+
"remark/",
|
|
10
11
|
"README.md",
|
|
11
12
|
"LICENSE"
|
|
12
13
|
],
|
|
@@ -44,7 +45,8 @@
|
|
|
44
45
|
"react-dom": "^18.0.0"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
|
-
"fs-extra": "^11.0.0"
|
|
48
|
+
"fs-extra": "^11.0.0",
|
|
49
|
+
"unist-util-visit": "^5.0.0"
|
|
48
50
|
},
|
|
49
51
|
"engines": {
|
|
50
52
|
"node": ">=16.14"
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
const visit = require('unist-util-visit');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a remark plugin that automatically detects and replaces glossary terms in markdown
|
|
7
|
+
*
|
|
8
|
+
* @param {object} options - Plugin options
|
|
9
|
+
* @param {Array} options.terms - Array of glossary term objects with {term, definition}
|
|
10
|
+
* @param {string} options.glossaryPath - Path to glossary JSON file (optional, if terms not provided)
|
|
11
|
+
* @param {string} options.routePath - Route path to glossary page (default: '/glossary')
|
|
12
|
+
* @param {string} options.siteDir - Docusaurus site directory (required if using glossaryPath)
|
|
13
|
+
* @returns {function} Remark plugin function
|
|
14
|
+
*/
|
|
15
|
+
function remarkGlossaryTerms({
|
|
16
|
+
terms = [],
|
|
17
|
+
glossaryPath = null,
|
|
18
|
+
routePath = '/glossary',
|
|
19
|
+
siteDir = null
|
|
20
|
+
} = {}) {
|
|
21
|
+
let glossaryTerms = terms;
|
|
22
|
+
|
|
23
|
+
// If terms not provided, try to load from glossaryPath (synchronously)
|
|
24
|
+
if (!glossaryTerms.length && glossaryPath && siteDir) {
|
|
25
|
+
try {
|
|
26
|
+
const glossaryFilePath = path.resolve(siteDir, glossaryPath);
|
|
27
|
+
if (fs.existsSync(glossaryFilePath)) {
|
|
28
|
+
const glossaryData = JSON.parse(fs.readFileSync(glossaryFilePath, 'utf8'));
|
|
29
|
+
glossaryTerms = glossaryData.terms || [];
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.warn(`Failed to load glossary from ${glossaryPath}:`, error.message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Build a map of terms for efficient lookup
|
|
37
|
+
// Key: lowercase term, Value: term object with original case
|
|
38
|
+
const termMap = new Map();
|
|
39
|
+
glossaryTerms.forEach(termObj => {
|
|
40
|
+
if (termObj.term) {
|
|
41
|
+
termMap.set(termObj.term.toLowerCase(), termObj);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Sort terms by length (longest first) to avoid partial matches
|
|
46
|
+
// e.g., "Application Programming Interface" should match before "API"
|
|
47
|
+
const sortedTerms = Array.from(termMap.entries()).sort(
|
|
48
|
+
(a, b) => b[0].length - a[0].length
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// If no terms, return a no-op transformer
|
|
52
|
+
if (sortedTerms.length === 0) {
|
|
53
|
+
return (tree) => tree;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Recursively replace glossary terms in text
|
|
58
|
+
* Returns an array of text nodes and MDX components
|
|
59
|
+
*/
|
|
60
|
+
function replaceTermsInText(text, position) {
|
|
61
|
+
if (!text || !sortedTerms.length) {
|
|
62
|
+
return [{ type: 'text', value: text }];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const result = [];
|
|
66
|
+
let lastIndex = 0;
|
|
67
|
+
let textLower = text.toLowerCase();
|
|
68
|
+
|
|
69
|
+
// Find all matches
|
|
70
|
+
const matches = [];
|
|
71
|
+
for (const [lowerTerm, termObj] of sortedTerms) {
|
|
72
|
+
const term = termObj.term;
|
|
73
|
+
let searchIndex = 0;
|
|
74
|
+
|
|
75
|
+
while (searchIndex < textLower.length) {
|
|
76
|
+
const index = textLower.indexOf(lowerTerm, searchIndex);
|
|
77
|
+
if (index === -1) break;
|
|
78
|
+
|
|
79
|
+
// Check if it's a whole word match
|
|
80
|
+
const beforeChar = index > 0 ? textLower[index - 1] : ' ';
|
|
81
|
+
const afterChar = index + lowerTerm.length < textLower.length
|
|
82
|
+
? textLower[index + lowerTerm.length]
|
|
83
|
+
: ' ';
|
|
84
|
+
|
|
85
|
+
// Word boundary check (alphanumeric characters)
|
|
86
|
+
const isWordBoundary =
|
|
87
|
+
!/\w/.test(beforeChar) && !/\w/.test(afterChar);
|
|
88
|
+
|
|
89
|
+
if (isWordBoundary) {
|
|
90
|
+
matches.push({
|
|
91
|
+
index,
|
|
92
|
+
length: term.length,
|
|
93
|
+
term: term,
|
|
94
|
+
termObj: termObj,
|
|
95
|
+
// Store original case from the text
|
|
96
|
+
originalText: text.substring(index, index + term.length)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
searchIndex = index + 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Sort matches by index
|
|
105
|
+
matches.sort((a, b) => a.index - b.index);
|
|
106
|
+
|
|
107
|
+
// Remove overlapping matches (keep the first one)
|
|
108
|
+
const nonOverlappingMatches = [];
|
|
109
|
+
let lastMatchEnd = 0;
|
|
110
|
+
for (const match of matches) {
|
|
111
|
+
if (match.index >= lastMatchEnd) {
|
|
112
|
+
nonOverlappingMatches.push(match);
|
|
113
|
+
lastMatchEnd = match.index + match.length;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Build result array
|
|
118
|
+
for (const match of nonOverlappingMatches) {
|
|
119
|
+
// Add text before match
|
|
120
|
+
if (match.index > lastIndex) {
|
|
121
|
+
result.push({
|
|
122
|
+
type: 'text',
|
|
123
|
+
value: text.substring(lastIndex, match.index)
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Add MDX component for glossary term
|
|
128
|
+
result.push({
|
|
129
|
+
type: 'mdxJsxFlowElement',
|
|
130
|
+
name: 'GlossaryTerm',
|
|
131
|
+
attributes: [
|
|
132
|
+
{
|
|
133
|
+
type: 'mdxJsxAttribute',
|
|
134
|
+
name: 'term',
|
|
135
|
+
value: match.termObj.term
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'mdxJsxAttribute',
|
|
139
|
+
name: 'definition',
|
|
140
|
+
value: match.termObj.definition || ''
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: 'mdxJsxAttribute',
|
|
144
|
+
name: 'routePath',
|
|
145
|
+
value: routePath
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
children: [
|
|
149
|
+
{
|
|
150
|
+
type: 'text',
|
|
151
|
+
value: match.originalText
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
lastIndex = match.index + match.length;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Add remaining text
|
|
160
|
+
if (lastIndex < text.length) {
|
|
161
|
+
result.push({
|
|
162
|
+
type: 'text',
|
|
163
|
+
value: text.substring(lastIndex)
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return result.length > 0 ? result : [{ type: 'text', value: text }];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return (tree) => {
|
|
171
|
+
visit(tree, 'text', (node, index, parent) => {
|
|
172
|
+
// Skip text nodes inside code blocks, links, or existing MDX components
|
|
173
|
+
if (
|
|
174
|
+
parent.type === 'code' ||
|
|
175
|
+
parent.type === 'inlineCode' ||
|
|
176
|
+
parent.type === 'link' ||
|
|
177
|
+
parent.type === 'mdxJsxFlowElement' ||
|
|
178
|
+
parent.type === 'mdxJsxTextElement'
|
|
179
|
+
) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Replace terms in text node
|
|
184
|
+
const replacements = replaceTermsInText(node.value);
|
|
185
|
+
|
|
186
|
+
// If we have replacements, replace the single text node with multiple nodes
|
|
187
|
+
if (replacements.length > 1 ||
|
|
188
|
+
(replacements.length === 1 && replacements[0].type !== 'text')) {
|
|
189
|
+
// Convert to text elements for paragraph context if needed
|
|
190
|
+
const newNodes = replacements.map(replacement => {
|
|
191
|
+
if (replacement.type === 'mdxJsxFlowElement') {
|
|
192
|
+
// In paragraph context, we need mdxJsxTextElement instead
|
|
193
|
+
if (parent.type === 'paragraph') {
|
|
194
|
+
return {
|
|
195
|
+
type: 'mdxJsxTextElement',
|
|
196
|
+
name: replacement.name,
|
|
197
|
+
attributes: replacement.attributes,
|
|
198
|
+
children: replacement.children
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return replacement;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Replace the single node with multiple nodes
|
|
206
|
+
parent.children.splice(index, 1, ...newNodes);
|
|
207
|
+
return index + newNodes.length - 1; // Return new index to continue
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = remarkGlossaryTerms;
|
|
214
|
+
|
|
@@ -14,9 +14,10 @@ import styles from './styles.module.css';
|
|
|
14
14
|
* @param {object} props
|
|
15
15
|
* @param {string} props.term - The glossary term
|
|
16
16
|
* @param {string} props.definition - The definition to show in tooltip
|
|
17
|
+
* @param {string} props.routePath - Route path to glossary page (default: '/glossary')
|
|
17
18
|
* @param {React.ReactNode} props.children - Optional custom display text
|
|
18
19
|
*/
|
|
19
|
-
export default function GlossaryTerm({ term, definition, children }) {
|
|
20
|
+
export default function GlossaryTerm({ term, definition, routePath = '/glossary', children }) {
|
|
20
21
|
const [showTooltip, setShowTooltip] = useState(false);
|
|
21
22
|
|
|
22
23
|
const displayText = children || term;
|
|
@@ -25,7 +26,7 @@ export default function GlossaryTerm({ term, definition, children }) {
|
|
|
25
26
|
return (
|
|
26
27
|
<span className={styles.glossaryTermWrapper}>
|
|
27
28
|
<a
|
|
28
|
-
href={
|
|
29
|
+
href={`${routePath}#${termId}`}
|
|
29
30
|
className={styles.glossaryTerm}
|
|
30
31
|
onMouseEnter={() => setShowTooltip(true)}
|
|
31
32
|
onMouseLeave={() => setShowTooltip(false)}
|