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 +227 -111
- package/index.js +84 -2
- package/package.json +4 -2
- package/remark/glossary-terms.js +214 -0
- package/theme/GlossaryTerm/index.js +3 -2
- package/theme/GlossaryTerm/styles.module.css +3 -1
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
|
-
##
|
|
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
|
-
|
|
18
|
+
1. **Install the plugin:**
|
|
19
|
+
```bash
|
|
20
|
+
npm install docusaurus-plugin-glossary
|
|
23
21
|
```
|
|
24
22
|
|
|
25
|
-
2. Add
|
|
23
|
+
2. **Add to your `docusaurus.config.js`:**
|
|
26
24
|
```javascript
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
66
|
-
|
|
43
|
+
3. **Create your glossary file at `glossary/glossary.json`:**
|
|
67
44
|
```json
|
|
68
45
|
{
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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.
|
|
86
|
-
|
|
64
|
+
4. **Start your dev server:**
|
|
87
65
|
```bash
|
|
88
|
-
npm
|
|
66
|
+
npm run start
|
|
89
67
|
```
|
|
90
68
|
|
|
91
|
-
5.
|
|
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
|
-
|
|
94
|
-
npm install docusaurus-plugin-glossary
|
|
95
|
-
```
|
|
74
|
+
## Installation
|
|
96
75
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
138
|
+
**Advanced: Manual Remark Plugin Configuration**
|
|
139
139
|
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
201
|
+
This website uses an <GlossaryTerm term="API">API</GlossaryTerm> to fetch data.
|
|
154
202
|
|
|
155
|
-
// Or with
|
|
156
|
-
We use <GlossaryTerm term="REST" definition="Representational State Transfer" /> for our
|
|
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
|
-
|
|
206
|
+
// Or with children content:
|
|
207
|
+
Our <GlossaryTerm term="API" definition="Application Programming Interface">RESTful API</GlossaryTerm> is available.
|
|
208
|
+
```
|
|
160
209
|
|
|
161
|
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
|
181
|
-
| -------------- |
|
|
182
|
-
| `glossaryPath` | string
|
|
183
|
-
| `routePath` | string
|
|
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
|
-
|
|
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
|
-
###
|
|
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"
|
|
256
|
-
uses <GlossaryTerm term="REST"
|
|
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 {
|
|
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: '
|
|
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.
|
|
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={
|
|
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:
|
|
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 {
|