create-trellis-docs 1.0.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.
Files changed (148) hide show
  1. package/bin/index.js +29 -0
  2. package/lib/index.js +178 -0
  3. package/package.json +23 -0
  4. package/template/_gitignore +6 -0
  5. package/template/blog/2025-01-01-welcome.md +15 -0
  6. package/template/design-tokens.json +218 -0
  7. package/template/docs/faq/general.mdx +16 -0
  8. package/template/docs/faq/index.mdx +13 -0
  9. package/template/docs/getting-started.mdx +54 -0
  10. package/template/docs/guides/writing-docs.mdx +90 -0
  11. package/template/docusaurus.config.js.tpl +200 -0
  12. package/template/package.json.tpl +60 -0
  13. package/template/packages/faq-index-plugin/README.md +104 -0
  14. package/template/packages/faq-index-plugin/index.js +91 -0
  15. package/template/packages/faq-index-plugin/package.json +15 -0
  16. package/template/packages/redirects-plugin/README.md +186 -0
  17. package/template/packages/redirects-plugin/index.js +167 -0
  18. package/template/packages/redirects-plugin/package.json +15 -0
  19. package/template/packages/redirects-plugin/yarn.lock +31 -0
  20. package/template/redirects.json +1 -0
  21. package/template/scripts/build-tokens.js +34 -0
  22. package/template/sidebars.js +17 -0
  23. package/template/src/components/CustomSearch/CustomSearch.js +241 -0
  24. package/template/src/components/CustomSearch/CustomSearchContent.js +171 -0
  25. package/template/src/components/CustomSearch/NavbarSearch.js +211 -0
  26. package/template/src/components/CustomSearch/SearchContext.js +26 -0
  27. package/template/src/components/CustomSearch/styles.module.css +171 -0
  28. package/template/src/components/CustomSearch/wrapperInit.js +11 -0
  29. package/template/src/components/FaqTableOfContents/index.jsx +176 -0
  30. package/template/src/components/FaqTableOfContents/styles.module.css +167 -0
  31. package/template/src/components/Feedback/Feedback.js +310 -0
  32. package/template/src/components/Feedback/api.js +77 -0
  33. package/template/src/components/FlippingCard/FlippingCard.js +197 -0
  34. package/template/src/components/FlippingCard/styles.module.css +248 -0
  35. package/template/src/components/Glossary.js +57 -0
  36. package/template/src/css/custom.css +765 -0
  37. package/template/src/data/glossary.json +1 -0
  38. package/template/src/pages/index.js.tpl +38 -0
  39. package/template/src/theme/Admonition/Icon/Danger.js +11 -0
  40. package/template/src/theme/Admonition/Icon/Info.js +11 -0
  41. package/template/src/theme/Admonition/Icon/Note.js +11 -0
  42. package/template/src/theme/Admonition/Icon/Tip.js +11 -0
  43. package/template/src/theme/Admonition/Icon/Warning.js +11 -0
  44. package/template/src/theme/Admonition/Layout/index.js +39 -0
  45. package/template/src/theme/Admonition/Layout/styles.module.css +36 -0
  46. package/template/src/theme/Admonition/Type/Caution.js +28 -0
  47. package/template/src/theme/Admonition/Type/Danger.js +26 -0
  48. package/template/src/theme/Admonition/Type/Info.js +26 -0
  49. package/template/src/theme/Admonition/Type/Note.js +26 -0
  50. package/template/src/theme/Admonition/Type/Tip.js +26 -0
  51. package/template/src/theme/Admonition/Type/Warning.js +26 -0
  52. package/template/src/theme/Admonition/Types.js +27 -0
  53. package/template/src/theme/Admonition/index.js +18 -0
  54. package/template/src/theme/AnnouncementBar/CloseButton/index.js +20 -0
  55. package/template/src/theme/AnnouncementBar/CloseButton/styles.module.css +4 -0
  56. package/template/src/theme/AnnouncementBar/Content/index.js +17 -0
  57. package/template/src/theme/AnnouncementBar/Content/styles.module.css +10 -0
  58. package/template/src/theme/AnnouncementBar/index.js +33 -0
  59. package/template/src/theme/AnnouncementBar/styles.module.css +55 -0
  60. package/template/src/theme/BlogSidebar/Content/index.js +35 -0
  61. package/template/src/theme/BlogSidebar/Desktop/index.js +44 -0
  62. package/template/src/theme/BlogSidebar/Desktop/styles.module.css +60 -0
  63. package/template/src/theme/BlogSidebar/Mobile/index.js +38 -0
  64. package/template/src/theme/BlogSidebar/Mobile/styles.module.css +3 -0
  65. package/template/src/theme/BlogSidebar/index.js +15 -0
  66. package/template/src/theme/Details/index.js +23 -0
  67. package/template/src/theme/Details/styles.module.css +52 -0
  68. package/template/src/theme/DocBreadcrumbs/Items/Home/index.js +22 -0
  69. package/template/src/theme/DocBreadcrumbs/Items/Home/styles.module.css +7 -0
  70. package/template/src/theme/DocBreadcrumbs/StructuredData/index.js +15 -0
  71. package/template/src/theme/DocBreadcrumbs/index.js +75 -0
  72. package/template/src/theme/DocBreadcrumbs/styles.module.css +5 -0
  73. package/template/src/theme/DocCard/index.js +93 -0
  74. package/template/src/theme/DocCard/styles.module.css +53 -0
  75. package/template/src/theme/DocCardList/index.js +27 -0
  76. package/template/src/theme/DocCardList/styles.module.css +7 -0
  77. package/template/src/theme/DocItem/Content/index.js +121 -0
  78. package/template/src/theme/DocItem/Content/styles.module.css +96 -0
  79. package/template/src/theme/DocItem/Footer/index.js +43 -0
  80. package/template/src/theme/DocItem/Footer/styles.module.css +19 -0
  81. package/template/src/theme/DocItem/Layout/index.js +55 -0
  82. package/template/src/theme/DocItem/Layout/styles.module.css +14 -0
  83. package/template/src/theme/DocItem/Metadata/index.js +17 -0
  84. package/template/src/theme/DocItem/Paginator/index.js +17 -0
  85. package/template/src/theme/DocItem/TOC/Desktop/index.js +15 -0
  86. package/template/src/theme/DocItem/TOC/Mobile/index.js +17 -0
  87. package/template/src/theme/DocItem/TOC/Mobile/styles.module.css +12 -0
  88. package/template/src/theme/DocItem/index.js +19 -0
  89. package/template/src/theme/DocItem/styles.module.css +28 -0
  90. package/template/src/theme/DocRoot/Layout/Main/index.js +23 -0
  91. package/template/src/theme/DocRoot/Layout/Main/styles.module.css +31 -0
  92. package/template/src/theme/DocRoot/Layout/Sidebar/ExpandButton/index.js +28 -0
  93. package/template/src/theme/DocRoot/Layout/Sidebar/ExpandButton/styles.module.css +27 -0
  94. package/template/src/theme/DocRoot/Layout/Sidebar/index.js +70 -0
  95. package/template/src/theme/DocRoot/Layout/Sidebar/styles.module.css +32 -0
  96. package/template/src/theme/DocRoot/Layout/index.js +27 -0
  97. package/template/src/theme/DocRoot/Layout/styles.module.css +9 -0
  98. package/template/src/theme/DocRoot/index.js +25 -0
  99. package/template/src/theme/DocSidebar/Desktop/CollapseButton/index.js +28 -0
  100. package/template/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css +40 -0
  101. package/template/src/theme/DocSidebar/Desktop/Content/index.js +44 -0
  102. package/template/src/theme/DocSidebar/Desktop/Content/styles.module.css +16 -0
  103. package/template/src/theme/DocSidebar/Desktop/index.js +28 -0
  104. package/template/src/theme/DocSidebar/Desktop/styles.module.css +37 -0
  105. package/template/src/theme/DocSidebar/Mobile/index.js +39 -0
  106. package/template/src/theme/DocSidebar/index.js +18 -0
  107. package/template/src/theme/DocSidebarItem/Category/index.js +203 -0
  108. package/template/src/theme/DocSidebarItem/Html/index.js +20 -0
  109. package/template/src/theme/DocSidebarItem/Html/styles.module.css +6 -0
  110. package/template/src/theme/DocSidebarItem/Link/index.js +49 -0
  111. package/template/src/theme/DocSidebarItem/Link/styles.module.css +3 -0
  112. package/template/src/theme/DocSidebarItem/index.js +15 -0
  113. package/template/src/theme/EditMetaRow/index.js +25 -0
  114. package/template/src/theme/EditMetaRow/styles.module.css +11 -0
  115. package/template/src/theme/EditThisPage/index.js +29 -0
  116. package/template/src/theme/ErrorPageContent.js +34 -0
  117. package/template/src/theme/Footer/Copyright/index.js +11 -0
  118. package/template/src/theme/Footer/Layout/index.js +21 -0
  119. package/template/src/theme/Footer/LinkItem/index.js +26 -0
  120. package/template/src/theme/Footer/Links/MultiColumn/index.js +44 -0
  121. package/template/src/theme/Footer/Links/Simple/index.js +32 -0
  122. package/template/src/theme/Footer/Links/index.js +11 -0
  123. package/template/src/theme/Footer/Logo/index.js +35 -0
  124. package/template/src/theme/Footer/Logo/styles.module.css +9 -0
  125. package/template/src/theme/Footer/index.js +22 -0
  126. package/template/src/theme/MDXComponents/Heading.js +120 -0
  127. package/template/src/theme/MDXComponents/index.js +17 -0
  128. package/template/src/theme/MDXComponents/styles.module.css +110 -0
  129. package/template/src/theme/MDXContent/index.js +6 -0
  130. package/template/src/theme/NavbarItem/DropdownNavbarItem/Desktop/index.js +110 -0
  131. package/template/src/theme/NavbarItem/DropdownNavbarItem/Mobile/index.js +136 -0
  132. package/template/src/theme/NavbarItem/DropdownNavbarItem/Mobile/styles.module.css +3 -0
  133. package/template/src/theme/NavbarItem/DropdownNavbarItem/index.js +7 -0
  134. package/template/src/theme/NotFound/Content/index.js +46 -0
  135. package/template/src/theme/NotFound/index.js +19 -0
  136. package/template/src/theme/NotFound.js +49 -0
  137. package/template/src/theme/PaginatorNavLink/index.js +17 -0
  138. package/template/src/theme/TOC/index.js +19 -0
  139. package/template/src/theme/TOC/styles.module.css +16 -0
  140. package/template/src/theme/TOCItems/Tree.js +30 -0
  141. package/template/src/theme/TOCItems/index.js +47 -0
  142. package/template/src/theme/TabItem/index.js +15 -0
  143. package/template/src/theme/TabItem/styles.module.css +10 -0
  144. package/template/src/theme/Tabs/index.js +189 -0
  145. package/template/src/theme/Tabs/styles.module.css +74 -0
  146. package/template/static/img/favicon.svg +4 -0
  147. package/template/static/img/oops-404.svg +11 -0
  148. package/template/static/searchIndex.json +1 -0
@@ -0,0 +1,200 @@
1
+ // @ts-check
2
+ import {themes as prismThemes} from 'prism-react-renderer';
3
+
4
+ const path = require('path');
5
+
6
+ /** @type {import('@docusaurus/types').Config} */
7
+ const config = {
8
+ title: "{{projectName}}",
9
+ tagline: "{{tagline}}",
10
+ url: "{{siteUrl}}",
11
+ baseUrl: "/",
12
+ trailingSlash: true,
13
+ onBrokenLinks: "warn",
14
+ onBrokenMarkdownLinks: "warn",
15
+ favicon: "img/favicon.svg",
16
+ organizationName: "",
17
+ projectName: "{{projectSlug}}",
18
+
19
+ customFields: {
20
+ repoUrl: '{{repoUrl}}',
21
+ },
22
+
23
+ markdown: {
24
+ mermaid: true,
25
+ },
26
+
27
+ themes: [
28
+ '@docusaurus/theme-mermaid',
29
+ '@docusaurus/theme-live-codeblock',
30
+ ],
31
+
32
+ staticDirectories: ['static'],
33
+
34
+ i18n: {
35
+ defaultLocale: 'en',
36
+ locales: ['en'],
37
+ },
38
+
39
+ presets: [
40
+ [
41
+ 'classic',
42
+ /** @type {import('@docusaurus/preset-classic').Options} */
43
+ ({
44
+ docs: {
45
+ breadcrumbs: true,
46
+ sidebarPath: './sidebars.js',
47
+ routeBasePath: '/',
48
+ path: 'docs',
49
+ showLastUpdateTime: true,
50
+ editUrl: '{{repoUrl}}/edit/main',
51
+ },
52
+ blog: {
53
+ showReadingTime: false,
54
+ },
55
+ theme: {
56
+ customCss: [
57
+ './src/css/tokens.css',
58
+ './src/css/custom.css',
59
+ ],
60
+ },
61
+ }),
62
+ ],
63
+ ],
64
+
65
+ plugins: [
66
+ '@r74tech/docusaurus-plugin-panzoom',
67
+ [
68
+ './packages/faq-index-plugin',
69
+ {
70
+ faqDir: 'docs/faq',
71
+ basePermalink: '/faq',
72
+ },
73
+ ],
74
+ function webpackPlugin(context, options) {
75
+ return {
76
+ name: 'webpack-config-plugin',
77
+ configureWebpack(config, isServer, utils) {
78
+ return {
79
+ resolve: {
80
+ extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
81
+ extensionAlias: {
82
+ '.js': ['.js', '.jsx'],
83
+ '.mjs': ['.mjs', '.mjs'],
84
+ '.cjs': ['.cjs', '.cjs'],
85
+ },
86
+ },
87
+ };
88
+ },
89
+ };
90
+ },
91
+ [
92
+ 'redirects-plugin',
93
+ {
94
+ redirectsFile: 'redirects.json'
95
+ }
96
+ ],
97
+ [
98
+ 'smart-search-plugin',
99
+ {
100
+ excludedFolders: ['includes', '_includes'],
101
+ excludedPrefixes: ['_'],
102
+ searchWeights: {
103
+ title: 1.0,
104
+ 'sections.heading': 1.0,
105
+ keywords: 0.8,
106
+ description: 0.6,
107
+ 'sections.content': 0.5,
108
+ content: 0.4
109
+ }
110
+ }
111
+ ],
112
+ [
113
+ 'lightbox-image-plugin',
114
+ {
115
+ selector: '.markdown img',
116
+ background: 'rgba(0, 0, 0, 0.8)',
117
+ zIndex: 999,
118
+ margin: 10,
119
+ scrollOffset: 10
120
+ }
121
+ ]
122
+ ],
123
+
124
+ themeConfig:
125
+ /** @type {import('@docusaurus/preset-classic').ThemeConfig} */
126
+ ({
127
+ docs: {
128
+ sidebar: {
129
+ hideable: true,
130
+ autoCollapseCategories: false,
131
+ }
132
+ },
133
+ colorMode: {
134
+ defaultMode: 'dark',
135
+ disableSwitch: false,
136
+ respectPrefersColorScheme: true,
137
+ },
138
+ zoom: {
139
+ selectors: ['div.mermaid[data-processed="true"]', 'div.docusaurus-mermaid-container', '.drawio'],
140
+ wrap: true,
141
+ timeout: 1000,
142
+ toolbar: {
143
+ enabled: true,
144
+ position: 'top-right',
145
+ opacity: 0,
146
+ },
147
+ enableWheelZoom: true,
148
+ enableWheelZoomWithShift: false,
149
+ enableDoubleClickResetZoom: true,
150
+ restrictZoomOutBeyondOrigin: false,
151
+ },
152
+ metadata: [
153
+ { property: 'og:description', content: '{{projectName}} — {{tagline}}' },
154
+ ],
155
+ navbar: {
156
+ title: '{{projectName}}',
157
+ logo: {
158
+ alt: '{{projectName}} Logo',
159
+ src: 'img/favicon.svg',
160
+ className: 'header-logo',
161
+ },
162
+ items: [
163
+ {
164
+ to: '/guides/writing-docs',
165
+ label: 'Guides',
166
+ position: 'right',
167
+ },
168
+ {
169
+ type: 'dropdown',
170
+ label: 'Resources',
171
+ position: 'right',
172
+ items: [
173
+ {
174
+ to: '/blog',
175
+ label: 'Release Notes',
176
+ },
177
+ {
178
+ to: '/faq',
179
+ label: 'FAQs',
180
+ },
181
+ ],
182
+ },
183
+ {
184
+ type: 'search',
185
+ position: 'right',
186
+ },
187
+ ],
188
+ },
189
+ footer: {
190
+ style: 'dark',
191
+ copyright: `\u00a9 ${new Date().getFullYear()} {{projectName}}   |   Powered by Trellis`,
192
+ },
193
+ prism: {
194
+ theme: prismThemes.github,
195
+ darkTheme: prismThemes.dracula,
196
+ },
197
+ }),
198
+ };
199
+
200
+ export default config;
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "{{projectSlug}}",
3
+ "license": "MIT",
4
+ "version": "1.0.0",
5
+ "private": true,
6
+ "scripts": {
7
+ "build-tokens": "node scripts/build-tokens.js",
8
+ "docusaurus": "npm run build-tokens && docusaurus",
9
+ "start": "npm run build-tokens && docusaurus start --port 3001",
10
+ "build": "npm run build-tokens && docusaurus build",
11
+ "serve": "docusaurus serve",
12
+ "clear": "docusaurus clear"
13
+ },
14
+ "dependencies": {
15
+ "@ant-design/icons": "^5.3.7",
16
+ "@docusaurus/core": "^3.9.2",
17
+ "@docusaurus/mdx-loader": "^3.9.2",
18
+ "@docusaurus/preset-classic": "^3.9.2",
19
+ "@docusaurus/theme-live-codeblock": "^3.9.2",
20
+ "@docusaurus/theme-mermaid": "3.9.2",
21
+ "@emotion/react": "^11.13.3",
22
+ "@emotion/styled": "^11.13.0",
23
+ "@mdx-js/react": "^3.1.1",
24
+ "@mermaid-js/layout-elk": "^0.1.9",
25
+ "@mui/icons-material": "^5.10.6",
26
+ "@mui/material": "^5.16.4",
27
+ "@r74tech/docusaurus-plugin-panzoom": "^2.4.0",
28
+ "antd": "^5.27.3",
29
+ "clsx": "^2.0.0",
30
+ "fuse.js": "^7.1.0",
31
+ "gray-matter": "^4.0.3",
32
+ "lightbox-image-plugin": "^1.0.1",
33
+ "mermaid": "^11.11.0",
34
+ "prism-react-renderer": "^2.4.1",
35
+ "react": "^19.0.0",
36
+ "react-dom": "^19.0.0",
37
+ "react-router-dom": "^5.3.4",
38
+ "redirects-plugin": "file:./packages/redirects-plugin",
39
+ "docusaurus-plugin-faq-index": "file:./packages/faq-index-plugin",
40
+ "smart-search-plugin": "^3.1.1",
41
+ "webpack": "^5.104.1",
42
+ "yaml": "^2.8.1"
43
+ },
44
+ "devDependencies": {
45
+ "@docusaurus/module-type-aliases": "3.9.2",
46
+ "@docusaurus/types": "3.9.2"
47
+ },
48
+ "browserslist": {
49
+ "production": [
50
+ ">0.5%",
51
+ "not dead",
52
+ "not op_mini all"
53
+ ],
54
+ "development": [
55
+ "last 3 chrome version",
56
+ "last 3 firefox version",
57
+ "last 5 safari version"
58
+ ]
59
+ }
60
+ }
@@ -0,0 +1,104 @@
1
+ # FAQ Index Plugin for Docusaurus
2
+
3
+ A Docusaurus 3.x local plugin that scans `/docs/faq/*.mdx` at build time, extracts questions from `###` headings, and exposes the full FAQ index via `setGlobalData`. Designed to work with the `FaqTableOfContents` component.
4
+
5
+ ## Features
6
+
7
+ - 🔍 Auto-discovery of FAQ files in `docs/faq/`
8
+ - 📋 Extracts questions from `###` headings with frontmatter parsing
9
+ - 🔗 Generates deep-link anchors matching Docusaurus conventions
10
+ - 📦 Exposes data via `useGlobalData` for client-side components
11
+
12
+ ## Installation
13
+
14
+ This is a local plugin included in the project under `packages/`. No separate installation is needed, but ensure `gray-matter` is available:
15
+
16
+ ```bash
17
+ cd packages/faq-index-plugin
18
+ yarn install
19
+ ```
20
+
21
+ ## Configuration
22
+
23
+ Add the plugin to `docusaurus.config.js`:
24
+
25
+ ```javascript
26
+ module.exports = {
27
+ plugins: [
28
+ [
29
+ './packages/faq-index-plugin',
30
+ {
31
+ faqDir: 'docs/faq', // path relative to site root
32
+ basePermalink: '/faq', // base URL path for FAQ pages
33
+ },
34
+ ],
35
+ ],
36
+ };
37
+ ```
38
+
39
+ ## Plugin options
40
+
41
+ | Option | Type | Default | Description |
42
+ |-----------------|----------|--------------|--------------------------------------------------|
43
+ | `faqDir` | `string` | `'docs/faq'` | Path to the FAQ folder relative to the site root. |
44
+ | `basePermalink` | `string` | `'/faq'` | Base permalink for generated FAQ links. |
45
+
46
+ ## How it works
47
+
48
+ 1. At build time, the plugin reads every `.mdx` and `.md` file in the configured FAQ directory (excluding `index.mdx` and `index.md`).
49
+ 2. It parses each file's frontmatter for `title` and `description` using `gray-matter`.
50
+ 3. It extracts question text from every `###` heading, stripping bold, italic, inline code, and link formatting.
51
+ 4. The full index is sorted alphabetically by topic title and exposed via Docusaurus `setGlobalData`.
52
+ 5. The `FaqTableOfContents` component reads this data at runtime using `useGlobalData`.
53
+
54
+ ## Expected FAQ file format
55
+
56
+ Each topic file should use `###` headings for questions:
57
+
58
+ ```mdx
59
+ ---
60
+ title: Secrets
61
+ description: Managing secrets, vaults, and credential rotation.
62
+ ---
63
+
64
+ # Secrets
65
+
66
+ ### How do I add a secret to my application?
67
+
68
+ Answer content here...
69
+
70
+ ### How do I rotate a secret?
71
+
72
+ Answer content here...
73
+ ```
74
+
75
+ ## Anchor generation
76
+
77
+ The plugin generates anchors from heading text using the same convention as Docusaurus: lowercase, special characters stripped, spaces replaced with hyphens. Deep links align automatically with Docusaurus-rendered anchors unless you use custom heading IDs (e.g., `### My heading {#custom-id}`).
78
+
79
+ ## File structure
80
+
81
+ ```
82
+ your-docusaurus-project/
83
+ ├── docs/
84
+ │ └── faq/
85
+ │ ├── index.mdx
86
+ │ ├── authentication-permissions.mdx
87
+ │ ├── database-infrastructure.mdx
88
+ │ └── secrets.mdx
89
+ ├── packages/
90
+ │ └── faq-index-plugin/
91
+ │ ├── package.json
92
+ │ ├── index.js
93
+ │ └── README.md
94
+ ├── src/
95
+ │ └── components/
96
+ │ └── FaqTableOfContents/
97
+ │ ├── index.jsx
98
+ │ └── styles.module.css
99
+ └── docusaurus.config.js
100
+ ```
101
+
102
+ ## License
103
+
104
+ MIT
@@ -0,0 +1,91 @@
1
+ // packages/faq-index-plugin/index.js
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const matter = require('gray-matter');
6
+
7
+ const PLUGIN_NAME = 'docusaurus-plugin-faq-index';
8
+
9
+ module.exports = function faqIndexPlugin(context, options) {
10
+ const opts = options || {};
11
+ const faqDir = path.resolve(context.siteDir, opts.faqDir || 'docs/faq');
12
+ const basePermalink = opts.basePermalink || '/faq';
13
+
14
+ return {
15
+ name: PLUGIN_NAME,
16
+
17
+ async loadContent() {
18
+ if (!fs.existsSync(faqDir)) {
19
+ console.warn(`[${PLUGIN_NAME}] FAQ directory not found: ${faqDir}`);
20
+ return [];
21
+ }
22
+
23
+ const files = fs
24
+ .readdirSync(faqDir)
25
+ .filter((f) => /\.(mdx?)$/.test(f) && f !== 'index.mdx' && f !== 'index.md');
26
+
27
+ const topics = [];
28
+
29
+ for (const file of files) {
30
+ const filePath = path.join(faqDir, file);
31
+
32
+ let raw;
33
+ try {
34
+ raw = fs.readFileSync(filePath, 'utf-8');
35
+ } catch (err) {
36
+ console.warn(`[${PLUGIN_NAME}] Could not read ${filePath}:`, err.message);
37
+ continue;
38
+ }
39
+
40
+ const { data: frontmatter, content } = matter(raw);
41
+
42
+ const slug = file.replace(/\.mdx?$/, '');
43
+ const title =
44
+ frontmatter.title ||
45
+ slug
46
+ .replace(/-/g, ' ')
47
+ .replace(/\b\w/g, (c) => c.toUpperCase());
48
+
49
+ // Extract questions from ### headings
50
+ const h3Regex = /^###\s+(.+)$/gm;
51
+ const questions = [];
52
+ let match;
53
+
54
+ while ((match = h3Regex.exec(content)) !== null) {
55
+ let questionText = match[1]
56
+ .trim()
57
+ .replace(/\*{1,3}(.*?)\*{1,3}/g, '$1')
58
+ .replace(/`([^`]+)`/g, '$1')
59
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
60
+ .trim();
61
+
62
+ if (questionText) {
63
+ const anchor = questionText
64
+ .toLowerCase()
65
+ .replace(/[^\w\s-]/g, '')
66
+ .replace(/\s+/g, '-')
67
+ .replace(/-+/g, '-');
68
+
69
+ questions.push({ text: questionText, anchor });
70
+ }
71
+ }
72
+
73
+ topics.push({
74
+ slug,
75
+ title,
76
+ description: frontmatter.description || '',
77
+ permalink: `${basePermalink}/${slug}`,
78
+ questions,
79
+ });
80
+ }
81
+
82
+ topics.sort((a, b) => a.title.localeCompare(b.title));
83
+ return topics;
84
+ },
85
+
86
+ async contentLoaded({ content, actions }) {
87
+ const { setGlobalData } = actions;
88
+ setGlobalData({ topics: content || [] });
89
+ },
90
+ };
91
+ };
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "faq-index-plugin",
3
+ "version": "1.0.0",
4
+ "description": "A custom Docusaurus plugin that indexes FAQ questions from docs/faq/ at build time",
5
+ "main": "index.js",
6
+ "author": "Patricia McPhee",
7
+ "license": "MIT",
8
+ "peerDependencies": {
9
+ "@docusaurus/core": "3.9.2",
10
+ "react": "^19.0.0"
11
+ },
12
+ "dependencies": {
13
+ "gray-matter": "^4.0.3"
14
+ }
15
+ }
@@ -0,0 +1,186 @@
1
+ # Redirects Plugin for Docusaurus
2
+
3
+ A Docusaurus 3.x plugin that handles URL redirects by generating static HTML redirect pages during the build process.
4
+
5
+ ## Features
6
+
7
+ - 🔄 Create client-side redirects using meta refresh and JavaScript
8
+ - 📁 Support for multiple redirect configuration sources
9
+ - 🎯 Handles both file-based (`/path.html`) and directory-based (`/path/index.html`) redirects
10
+ - 🔍 Auto-detection of redirect configuration files
11
+ - ⚡ Zero runtime overhead - generates static HTML files
12
+
13
+ ## Installation
14
+
15
+ This is a local plugin included in the project. No separate installation needed.
16
+
17
+ ## Configuration
18
+
19
+ Add the plugin to your `docusaurus.config.js`:
20
+
21
+ ```javascript
22
+ module.exports = {
23
+ plugins: [
24
+ [
25
+ './packages/redirects-plugin',
26
+ {
27
+ // Optional: specify redirects directly in config
28
+ redirects: [
29
+ {
30
+ from: '/old-page',
31
+ to: '/new-page',
32
+ type: 301 // optional, defaults to 301
33
+ }
34
+ ],
35
+ // Optional: specify a custom redirects file location
36
+ redirectsFile: 'config/redirects.json'
37
+ }
38
+ ]
39
+ ]
40
+ };
41
+ ```
42
+
43
+ ## Redirect Configuration Sources
44
+
45
+ The plugin loads redirects from multiple sources in this order:
46
+
47
+ ### 1. Plugin Options
48
+
49
+ Define redirects directly in `docusaurus.config.js`:
50
+
51
+ ```javascript
52
+ {
53
+ redirects: [
54
+ { from: '/old', to: '/new' },
55
+ { from: '/legacy/path', to: '/modern/path', type: 302 }
56
+ ]
57
+ }
58
+ ```
59
+
60
+ ### 2. Custom File Location
61
+
62
+ Specify a custom file path:
63
+
64
+ ```javascript
65
+ {
66
+ redirectsFile: 'config/my-redirects.json'
67
+ }
68
+ ```
69
+
70
+ ### 3. Auto-Detection
71
+
72
+ If no `redirectsFile` is specified, the plugin automatically looks for:
73
+
74
+ - `redirects.json` (project root)
75
+ - `config/redirects.json`
76
+ - `src/redirects.json`
77
+
78
+ ## Redirects File Format
79
+
80
+ Create a `redirects.json` file:
81
+
82
+ ```json
83
+ [
84
+ {
85
+ "from": "/old-docs/getting-started",
86
+ "to": "/docs/introduction"
87
+ },
88
+ {
89
+ "from": "/api-v1",
90
+ "to": "/api/v2",
91
+ "type": 302
92
+ },
93
+ {
94
+ "from": "/blog/old-post",
95
+ "to": "https://external-site.com/new-post"
96
+ }
97
+ ]
98
+ ```
99
+
100
+ ## Redirect Object Properties
101
+
102
+ | Property | Type | Required | Default | Description |
103
+ |----------|------|----------|---------|-------------|
104
+ | `from` | string | Yes | - | Source path to redirect from |
105
+ | `to` | string | Yes | - | Destination URL or path |
106
+ | `type` | number | No | 301 | HTTP redirect status code |
107
+
108
+ ## How It Works
109
+
110
+ 1. During the build process, the plugin reads all configured redirects
111
+ 2. For each redirect, it generates HTML files with:
112
+ - Meta refresh tag for immediate redirect
113
+ - JavaScript fallback for better compatibility
114
+ - Canonical link tag for SEO
115
+ 3. Creates both `/path.html` and `/path/index.html` for maximum compatibility
116
+
117
+ ## Generated HTML Example
118
+
119
+ For a redirect from `/old` to `/new`, the plugin generates:
120
+
121
+ ```html
122
+ <!DOCTYPE html>
123
+ <html>
124
+ <head>
125
+ <meta charset="UTF-8">
126
+ <meta http-equiv="refresh" content="0; url=/new">
127
+ <link rel="canonical" href="/new" />
128
+ <title>Redirecting...</title>
129
+ </head>
130
+ <body>
131
+ <p>Redirecting to <a href="/new">/new</a>...</p>
132
+ <script>
133
+ window.location.href = '/new';
134
+ </script>
135
+ </body>
136
+ </html>
137
+ ```
138
+
139
+ ## Path Normalization
140
+
141
+ - Leading slashes are added if missing
142
+ - Trailing slashes are removed for consistency
143
+ - Hash fragments are preserved
144
+ - External URLs (starting with `http`) are supported
145
+
146
+ ## Debugging
147
+
148
+ The plugin logs its activity during the build:
149
+
150
+ ```
151
+ Loaded 5 redirects from config
152
+ Auto-detected and loaded 10 redirects from redirects.json
153
+ Processing 15 redirects...
154
+ Created redirect: /old → /new (301)
155
+ ```
156
+
157
+ ## Common Use Cases
158
+
159
+ - Maintaining URLs after site restructuring
160
+ - Redirecting from legacy documentation versions
161
+ - Creating short URLs for marketing campaigns
162
+ - Handling moved or renamed pages
163
+ - Redirecting to external resources
164
+
165
+ ## Limitations
166
+
167
+ - Redirects only work for paths that don't correspond to actual files
168
+ - Server-side redirects are more efficient but require server configuration
169
+ - Meta refresh redirects may not preserve HTTP referrer information
170
+
171
+ ## Troubleshooting
172
+
173
+ **Redirects not working:**
174
+
175
+ - Check that the source path doesn't conflict with an existing page
176
+ - Verify the JSON file is valid (use a JSON validator)
177
+ - Check build logs for error messages
178
+
179
+ **Invalid redirect warnings:**
180
+
181
+ - Ensure both `from` and `to` fields are present
182
+ - Check for typos in field names
183
+
184
+ ## License
185
+
186
+ MIT