docusaurus-plugin-glossary 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,111 +7,83 @@ 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
13
14
  - **Customizable**: Configure glossary path and route
14
15
 
15
- ## Installation
16
-
17
- ### For Local Use (Same Repository)
18
-
19
- 1. Copy the plugin directory to your Docusaurus site:
16
+ ## Quick Start
20
17
 
21
- ```
22
- src/plugins/docusaurus-plugin-glossary/
18
+ 1. **Install the plugin:**
19
+ ```bash
20
+ npm install docusaurus-plugin-glossary
23
21
  ```
24
22
 
25
- 2. Add the plugin to your `docusaurus.config.js`:
23
+ 2. **Add to your `docusaurus.config.js`:**
26
24
  ```javascript
27
- plugins: [
28
- [
29
- require.resolve('./src/plugins/docusaurus-plugin-glossary'),
30
- {
31
- glossaryPath: 'glossary/glossary.json', // optional, default: 'glossary/glossary.json'
32
- routePath: '/glossary', // optional, default: '/glossary'
33
- },
25
+ module.exports = {
26
+ // ... other config
27
+ plugins: [
28
+ [
29
+ 'docusaurus-plugin-glossary',
30
+ {
31
+ glossaryPath: 'glossary/glossary.json', // Path to your glossary file
32
+ routePath: '/glossary', // URL path for glossary page
33
+ autoLinkTerms: true, // Automatically link terms (default: true)
34
+ },
35
+ ],
34
36
  ],
35
- ];
36
- ```
37
-
38
- ### For Separate Package (To Publish)
39
-
40
- To publish this as a separate npm package:
41
-
42
- 1. Create a new directory for the package:
43
-
44
- ```bash
45
- mkdir docusaurus-plugin-glossary
46
- cd docusaurus-plugin-glossary
47
- ```
48
-
49
- 2. Copy the plugin files:
50
-
51
- ```
52
- docusaurus-plugin-glossary/
53
- ├── index.js
54
- ├── components/
55
- │ ├── GlossaryPage.js
56
- │ └── GlossaryPage.module.css
57
- ├── theme/
58
- │ └── GlossaryTerm/
59
- │ ├── index.js
60
- │ └── styles.module.css
61
- ├── package.json
62
- └── README.md
37
+ // ... other config
38
+ };
63
39
  ```
40
+
41
+ **That's it!** The remark plugin is automatically configured - no manual `markdown.remarkPlugins` setup needed.
64
42
 
65
- 3. Create a `package.json`:
66
-
43
+ 3. **Create your glossary file at `glossary/glossary.json`:**
67
44
  ```json
68
45
  {
69
- "name": "docusaurus-plugin-glossary",
70
- "version": "1.0.0",
71
- "description": "A Docusaurus plugin for creating and managing glossary terms",
72
- "main": "index.js",
73
- "keywords": ["docusaurus", "glossary", "plugin", "documentation"],
74
- "peerDependencies": {
75
- "@docusaurus/core": "^3.0.0",
76
- "react": "^18.0.0",
77
- "react-dom": "^18.0.0"
78
- },
79
- "dependencies": {
80
- "fs-extra": "^11.0.0"
81
- }
46
+ "description": "A collection of technical terms and their definitions",
47
+ "terms": [
48
+ {
49
+ "term": "API",
50
+ "abbreviation": "Application Programming Interface",
51
+ "definition": "A set of rules and protocols that allows different software applications to communicate with each other.",
52
+ "relatedTerms": ["REST", "GraphQL"]
53
+ },
54
+ {
55
+ "term": "REST",
56
+ "abbreviation": "Representational State Transfer",
57
+ "definition": "An architectural style for designing networked applications.",
58
+ "relatedTerms": ["API", "HTTP"]
59
+ }
60
+ ]
82
61
  }
83
62
  ```
84
63
 
85
- 4. Publish to npm:
86
-
64
+ 4. **Start your dev server:**
87
65
  ```bash
88
- npm publish
66
+ npm run start
89
67
  ```
90
68
 
91
- 5. Install in your Docusaurus site:
69
+ 5. **That's it!**
70
+ - Visit `/glossary` to see your glossary page
71
+ - Write markdown normally - terms will automatically be linked with tooltips
72
+ - Use `<GlossaryTerm>` component in MDX for manual control
92
73
 
93
- ```bash
94
- npm install docusaurus-plugin-glossary
95
- ```
74
+ ## Installation
96
75
 
97
- 6. Add to your `docusaurus.config.js`:
98
- ```javascript
99
- plugins: [
100
- [
101
- 'docusaurus-plugin-glossary',
102
- {
103
- glossaryPath: 'glossary/glossary.json',
104
- routePath: '/glossary',
105
- },
106
- ],
107
- ];
108
- ```
76
+ ### Install from npm (Recommended)
77
+
78
+ ```bash
79
+ npm install docusaurus-plugin-glossary
80
+ ```
109
81
 
110
- ## Usage
82
+ ## Usage Guide
111
83
 
112
- ### 1. Create a Glossary Data File
84
+ ### Step 1: Create Your Glossary File
113
85
 
114
- Create a JSON file at `glossary/glossary.json` (or your configured path):
86
+ Create a JSON file at `glossary/glossary.json` (or your configured path) in your Docusaurus site root:
115
87
 
116
88
  ```json
117
89
  {
@@ -133,54 +105,148 @@ Create a JSON file at `glossary/glossary.json` (or your configured path):
133
105
  }
134
106
  ```
135
107
 
136
- ### 2. Glossary Data Structure
108
+ **Required fields:**
109
+ - `term` (string): The glossary term name
110
+ - `definition` (string): The term's definition
111
+
112
+ **Optional fields:**
113
+ - `abbreviation` (string): The full form if the term is an abbreviation
114
+ - `relatedTerms` (string[]): Array of related term names that link to other glossary entries
115
+ - `id` (string): Custom ID for linking (auto-generated from term name if not provided)
116
+
117
+ ### Step 2: Configure the Plugin
118
+
119
+ Add the plugin to your `docusaurus.config.js`:
120
+
121
+ ```javascript
122
+ module.exports = {
123
+ plugins: [
124
+ [
125
+ 'docusaurus-plugin-glossary',
126
+ {
127
+ glossaryPath: 'glossary/glossary.json', // Path to your glossary file
128
+ routePath: '/glossary', // URL path for the glossary page
129
+ autoLinkTerms: true, // Automatically detect and link terms (default: true)
130
+ },
131
+ ],
132
+ ],
133
+ };
134
+ ```
135
+
136
+ **Automatic Configuration:** The remark plugin is automatically configured when `autoLinkTerms` is `true` (the default). You don't need to manually configure `markdown.remarkPlugins`!
137
137
 
138
- Each term object can include:
138
+ **Advanced: Manual Remark Plugin Configuration**
139
139
 
140
- - `term` (required): The glossary term
141
- - `definition` (required): The term's definition
142
- - `abbreviation` (optional): The full form if the term is an abbreviation
143
- - `relatedTerms` (optional): Array of related term names
144
- - `id` (optional): Custom ID for linking (auto-generated from term if not provided)
140
+ If you need more control or want to disable automatic detection, you can manually configure the remark plugin:
145
141
 
146
- ### 3. Using the GlossaryTerm Component
142
+ ```javascript
143
+ const glossaryPlugin = require('docusaurus-plugin-glossary');
144
+
145
+ module.exports = {
146
+ plugins: [
147
+ [
148
+ 'docusaurus-plugin-glossary',
149
+ {
150
+ glossaryPath: 'glossary/glossary.json',
151
+ routePath: '/glossary',
152
+ autoLinkTerms: false, // Disable automatic configuration
153
+ },
154
+ ],
155
+ ],
156
+ markdown: {
157
+ remarkPlugins: [
158
+ [
159
+ glossaryPlugin.remarkPlugin,
160
+ {
161
+ glossaryPath: 'glossary/glossary.json',
162
+ routePath: '/glossary',
163
+ siteDir: process.cwd(),
164
+ },
165
+ ],
166
+ ],
167
+ },
168
+ };
169
+ ```
147
170
 
148
- Import and use the `GlossaryTerm` component in your MDX files:
171
+ ### Step 3: Use Glossary Terms in Your Content
172
+
173
+ #### Option A: Automatic Detection (Recommended)
174
+
175
+ With the remark plugin configured, glossary terms are **automatically detected and linked** in all your markdown files. Simply write your content normally:
176
+
177
+ ```markdown
178
+ Our API uses REST principles to provide a simple interface.
179
+
180
+ This project supports webhooks for real-time notifications.
181
+ ```
182
+
183
+ Terms like "API", "REST", and "webhooks" will automatically be:
184
+ - Detected if they're defined in your glossary
185
+ - Styled with a dotted underline
186
+ - Display a tooltip with the definition on hover
187
+ - Link to the full glossary page entry
188
+
189
+ **Limitations:**
190
+ - Only whole words are matched (respects word boundaries)
191
+ - Terms inside code blocks, links, or existing MDX components are **not** processed
192
+ - Matching is case-insensitive
193
+
194
+ #### Option B: Manual Component Usage
195
+
196
+ For more control or when automatic detection isn't sufficient, use the `GlossaryTerm` component in MDX files:
149
197
 
150
198
  ```jsx
151
199
  import GlossaryTerm from '@theme/GlossaryTerm';
152
200
 
153
- This website uses an <GlossaryTerm term="API" definition="Application Programming Interface">API</GlossaryTerm> to fetch data.
201
+ This website uses an <GlossaryTerm term="API">API</GlossaryTerm> to fetch data.
154
202
 
155
- // Or with default display (term name):
156
- We use <GlossaryTerm term="REST" definition="Representational State Transfer" /> for our web services.
157
- ```
203
+ // Or with explicit definition (overrides glossary entry):
204
+ We use <GlossaryTerm term="REST" definition="Representational State Transfer" /> for our services.
158
205
 
159
- The component features:
206
+ // Or with children content:
207
+ Our <GlossaryTerm term="API" definition="Application Programming Interface">RESTful API</GlossaryTerm> is available.
208
+ ```
160
209
 
161
- - Dotted underline styling
162
- - Tooltip showing definition on hover
163
- - Link to full glossary page entry
164
- - Accessible with keyboard navigation
210
+ **Component props:**
211
+ - `term` (required): The term name (used to look up definition from glossary)
212
+ - `definition` (optional): Override the definition from the glossary file
213
+ - `children` (optional): Custom text to display (defaults to term name)
165
214
 
166
- ### 4. Accessing the Glossary Page
215
+ ### Step 4: Access the Glossary Page
167
216
 
168
217
  The glossary page is automatically available at `/glossary` (or your configured `routePath`).
169
218
 
170
- Features:
171
-
219
+ **Features:**
172
220
  - Alphabetical grouping with letter navigation
173
- - Real-time search
174
- - Related terms linking
221
+ - Real-time search across terms and definitions
222
+ - Clickable related terms
175
223
  - Responsive design
176
224
  - Dark mode support
177
225
 
226
+ **Add to navigation:**
227
+
228
+ To add the glossary to your navbar, update your `docusaurus.config.js`:
229
+
230
+ ```javascript
231
+ module.exports = {
232
+ themeConfig: {
233
+ navbar: {
234
+ items: [
235
+ {to: '/glossary', label: 'Glossary', position: 'left'},
236
+ // ... other items
237
+ ],
238
+ },
239
+ },
240
+ };
241
+ ```
242
+
178
243
  ## Configuration Options
179
244
 
180
- | Option | Type | Default | Description |
181
- | -------------- | ------ | -------------------------- | ----------------------------------------------------- |
182
- | `glossaryPath` | string | `'glossary/glossary.json'` | Path to glossary JSON file relative to site directory |
183
- | `routePath` | string | `'/glossary'` | URL path for glossary page |
245
+ | Option | Type | Default | Description |
246
+ | -------------- | ------- | -------------------------- | ----------------------------------------------------- |
247
+ | `glossaryPath` | string | `'glossary/glossary.json'` | Path to glossary JSON file relative to site directory |
248
+ | `routePath` | string | `'/glossary'` | URL path for glossary page |
249
+ | `autoLinkTerms`| boolean | `true` | Enable automatic term detection in markdown (requires remark plugin configuration) |
184
250
 
185
251
  ## Customization
186
252
 
@@ -226,12 +292,26 @@ themeConfig: {
226
292
 
227
293
  ## Examples
228
294
 
229
- ### Example 1: Technical Documentation
295
+ See the [Usage Guide](#usage-guide) section above for complete examples. Here are additional examples:
296
+
297
+ ### Complete Glossary Example
230
298
 
231
299
  ```json
232
300
  {
233
301
  "description": "Technical terms used in our documentation",
234
302
  "terms": [
303
+ {
304
+ "term": "API",
305
+ "abbreviation": "Application Programming Interface",
306
+ "definition": "A set of rules and protocols that allows different software applications to communicate with each other.",
307
+ "relatedTerms": ["REST", "GraphQL", "Webhook"]
308
+ },
309
+ {
310
+ "term": "REST",
311
+ "abbreviation": "Representational State Transfer",
312
+ "definition": "An architectural style for designing networked applications.",
313
+ "relatedTerms": ["API", "HTTP"]
314
+ },
235
315
  {
236
316
  "term": "Webhook",
237
317
  "definition": "An HTTP callback that occurs when something happens; a simple event-notification via HTTP POST.",
@@ -241,7 +321,22 @@ themeConfig: {
241
321
  }
242
322
  ```
243
323
 
244
- ### Example 2: Using in MDX
324
+ ### Writing Content with Automatic Detection
325
+
326
+ ```markdown
327
+ ---
328
+ title: API Documentation
329
+ ---
330
+
331
+ # Getting Started with Our API
332
+
333
+ Our API uses RESTful principles to provide a simple and consistent interface.
334
+ Webhooks are supported for real-time event notifications.
335
+ ```
336
+
337
+ The terms "API", "RESTful", and "Webhooks" will automatically be detected and linked if they're defined in your glossary.
338
+
339
+ ### Using the Component in MDX
245
340
 
246
341
  ```mdx
247
342
  ---
@@ -252,8 +347,8 @@ import GlossaryTerm from '@theme/GlossaryTerm';
252
347
 
253
348
  # Getting Started with Our API
254
349
 
255
- Our <GlossaryTerm term="API" definition="Application Programming Interface" />
256
- uses <GlossaryTerm term="REST" definition="Representational State Transfer">RESTful</GlossaryTerm>
350
+ Our <GlossaryTerm term="API" />
351
+ uses <GlossaryTerm term="REST">RESTful</GlossaryTerm>
257
352
  principles to provide a simple and consistent interface.
258
353
  ```
259
354
 
@@ -267,6 +362,8 @@ docusaurus-plugin-glossary/
267
362
  ├── components/
268
363
  │ ├── GlossaryPage.js # Glossary page component
269
364
  │ └── GlossaryPage.module.css # Glossary page styles
365
+ ├── remark/
366
+ │ └── glossary-terms.js # Remark plugin for automatic term detection
270
367
  ├── theme/
271
368
  │ └── GlossaryTerm/
272
369
  │ ├── index.js # Term component
@@ -281,6 +378,15 @@ docusaurus-plugin-glossary/
281
378
  3. **getThemePath**: Exposes theme components
282
379
  4. **getPathsToWatch**: Watches glossary file for changes
283
380
 
381
+ ### Remark Plugin
382
+
383
+ The remark plugin (`remark/glossary-terms.js`) automatically detects glossary terms in markdown files and replaces them with `GlossaryTerm` components. It:
384
+
385
+ - Scans text nodes for glossary terms (case-insensitive, whole word matching)
386
+ - Replaces matching terms with MDX components that show tooltips
387
+ - Skips terms inside code blocks, links, or existing MDX components
388
+ - Respects word boundaries to avoid partial matches
389
+
284
390
  ## Troubleshooting
285
391
 
286
392
  ### Glossary page returns 404
@@ -301,6 +407,16 @@ docusaurus-plugin-glossary/
301
407
  - Try clearing cache with `npm run clear`
302
408
  - Restart dev server
303
409
 
410
+ ### Automatic term detection not working
411
+
412
+ - Ensure `autoLinkTerms` is `true` (the default) in your plugin configuration
413
+ - The remark plugin is automatically configured, so you don't need to manually add it to `markdown.remarkPlugins`
414
+ - Verify your glossary file exists at the configured `glossaryPath` and contains terms
415
+ - Check that terms in your content match the terms in your glossary (matching is case-insensitive but respects word boundaries)
416
+ - Try clearing cache with `npm run clear` and restarting the dev server
417
+ - Note that terms inside code blocks, links, or MDX components are not processed
418
+ - If you've manually configured the remark plugin, ensure `siteDir` points to the correct Docusaurus site directory
419
+
304
420
  ### Styles not applying
305
421
 
306
422
  - Check for CSS conflicts in your custom CSS
package/index.js CHANGED
@@ -9,29 +9,74 @@ 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 { glossaryPath = 'glossary/glossary.json', routePath = '/glossary' } = options;
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',
24
32
 
33
+ configureMarkdown(markdownConfig) {
34
+ // Automatically configure the remark plugin if autoLinkTerms is enabled
35
+ if (autoLinkTerms) {
36
+ if (!markdownConfig.remarkPlugins) {
37
+ markdownConfig.remarkPlugins = [];
38
+ }
39
+
40
+ const remarkPlugin = require('./remark/glossary-terms');
41
+
42
+ // Check if the remark plugin is already configured
43
+ const isAlreadyConfigured = markdownConfig.remarkPlugins.some(
44
+ (plugin) => {
45
+ if (Array.isArray(plugin) && plugin[0]) {
46
+ // Check if it's our remark plugin by comparing the function reference
47
+ return plugin[0] === remarkPlugin;
48
+ }
49
+ // Also check if it's directly the remark plugin function
50
+ return plugin === remarkPlugin;
51
+ }
52
+ );
53
+
54
+ // Only add if not already configured
55
+ if (!isAlreadyConfigured) {
56
+ markdownConfig.remarkPlugins.push([
57
+ remarkPlugin,
58
+ {
59
+ glossaryPath,
60
+ routePath,
61
+ siteDir: context.siteDir,
62
+ }
63
+ ]);
64
+ }
65
+ }
66
+ },
67
+
25
68
  async loadContent() {
26
69
  // Load glossary terms from JSON file
27
70
  const glossaryFilePath = path.resolve(context.siteDir, glossaryPath);
28
71
 
29
72
  if (await fs.pathExists(glossaryFilePath)) {
30
73
  const glossaryData = await fs.readJson(glossaryFilePath);
74
+ glossaryDataCache = glossaryData;
31
75
  return glossaryData;
32
76
  }
33
77
 
34
78
  console.warn(`Glossary file not found at ${glossaryFilePath}. Using empty glossary.`);
79
+ glossaryDataCache = { terms: [] };
35
80
  return { terms: [] };
36
81
  },
37
82
 
@@ -40,11 +85,20 @@ function glossaryPlugin(context, options = {}) {
40
85
 
41
86
  // Create data file that can be imported by components
42
87
  const glossaryDataPath = await createData('glossary-data.json', JSON.stringify(content));
88
+
89
+ // Create a data file for the remark plugin to access glossary terms
90
+ const remarkGlossaryDataPath = await createData(
91
+ 'remark-glossary-data.json',
92
+ JSON.stringify({
93
+ terms: content.terms || [],
94
+ routePath: routePath,
95
+ })
96
+ );
43
97
 
44
98
  // Add glossary page route
45
99
  addRoute({
46
100
  path: routePath,
47
- component: '@site/src/plugins/docusaurus-plugin-glossary/components/GlossaryPage.js',
101
+ component: path.join(__dirname, 'components/GlossaryPage.js'),
48
102
  exact: true,
49
103
  modules: {
50
104
  glossaryData: glossaryDataPath,
@@ -67,4 +121,32 @@ function glossaryPlugin(context, options = {}) {
67
121
  };
68
122
  }
69
123
 
124
+ // Export remark plugin factory for use in markdown configuration
125
+ glossaryPlugin.remarkPlugin = require('./remark/glossary-terms');
126
+
127
+ /**
128
+ * Helper function to get the configured remark plugin
129
+ * This can be used in docusaurus.config.js markdown configuration
130
+ *
131
+ * @param {object} pluginOptions - Plugin options from docusaurus.config.js
132
+ * @param {object} context - Docusaurus context
133
+ * @returns {function} Configured remark plugin
134
+ */
135
+ glossaryPlugin.getRemarkPlugin = function(pluginOptions, context) {
136
+ const {
137
+ glossaryPath = 'glossary/glossary.json',
138
+ routePath = '/glossary',
139
+ siteDir = context.siteDir
140
+ } = pluginOptions;
141
+
142
+ return [
143
+ require('./remark/glossary-terms'),
144
+ {
145
+ glossaryPath,
146
+ routePath,
147
+ siteDir,
148
+ }
149
+ ];
150
+ };
151
+
70
152
  module.exports = glossaryPlugin;
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "docusaurus-plugin-glossary",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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={`/glossary#${termId}`}
29
+ href={`${routePath}#${termId}`}
29
30
  className={styles.glossaryTerm}
30
31
  onMouseEnter={() => setShowTooltip(true)}
31
32
  onMouseLeave={() => setShowTooltip(false)}
@@ -33,10 +33,11 @@
33
33
  white-space: normal;
34
34
  max-width: 300px;
35
35
  min-width: 200px;
36
- z-index: 1000;
36
+ z-index: 9999;
37
37
  opacity: 0;
38
38
  pointer-events: none;
39
39
  transition: opacity 0.2s;
40
+ visibility: hidden;
40
41
  }
41
42
 
42
43
  .tooltip::after {
@@ -63,6 +64,7 @@
63
64
  .tooltipVisible {
64
65
  opacity: 1;
65
66
  pointer-events: auto;
67
+ visibility: visible;
66
68
  }
67
69
 
68
70
  .tooltip strong {