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.
- package/bin/index.js +29 -0
- package/lib/index.js +178 -0
- package/package.json +23 -0
- package/template/_gitignore +6 -0
- package/template/blog/2025-01-01-welcome.md +15 -0
- package/template/design-tokens.json +218 -0
- package/template/docs/faq/general.mdx +16 -0
- package/template/docs/faq/index.mdx +13 -0
- package/template/docs/getting-started.mdx +54 -0
- package/template/docs/guides/writing-docs.mdx +90 -0
- package/template/docusaurus.config.js.tpl +200 -0
- package/template/package.json.tpl +60 -0
- package/template/packages/faq-index-plugin/README.md +104 -0
- package/template/packages/faq-index-plugin/index.js +91 -0
- package/template/packages/faq-index-plugin/package.json +15 -0
- package/template/packages/redirects-plugin/README.md +186 -0
- package/template/packages/redirects-plugin/index.js +167 -0
- package/template/packages/redirects-plugin/package.json +15 -0
- package/template/packages/redirects-plugin/yarn.lock +31 -0
- package/template/redirects.json +1 -0
- package/template/scripts/build-tokens.js +34 -0
- package/template/sidebars.js +17 -0
- package/template/src/components/CustomSearch/CustomSearch.js +241 -0
- package/template/src/components/CustomSearch/CustomSearchContent.js +171 -0
- package/template/src/components/CustomSearch/NavbarSearch.js +211 -0
- package/template/src/components/CustomSearch/SearchContext.js +26 -0
- package/template/src/components/CustomSearch/styles.module.css +171 -0
- package/template/src/components/CustomSearch/wrapperInit.js +11 -0
- package/template/src/components/FaqTableOfContents/index.jsx +176 -0
- package/template/src/components/FaqTableOfContents/styles.module.css +167 -0
- package/template/src/components/Feedback/Feedback.js +310 -0
- package/template/src/components/Feedback/api.js +77 -0
- package/template/src/components/FlippingCard/FlippingCard.js +197 -0
- package/template/src/components/FlippingCard/styles.module.css +248 -0
- package/template/src/components/Glossary.js +57 -0
- package/template/src/css/custom.css +765 -0
- package/template/src/data/glossary.json +1 -0
- package/template/src/pages/index.js.tpl +38 -0
- package/template/src/theme/Admonition/Icon/Danger.js +11 -0
- package/template/src/theme/Admonition/Icon/Info.js +11 -0
- package/template/src/theme/Admonition/Icon/Note.js +11 -0
- package/template/src/theme/Admonition/Icon/Tip.js +11 -0
- package/template/src/theme/Admonition/Icon/Warning.js +11 -0
- package/template/src/theme/Admonition/Layout/index.js +39 -0
- package/template/src/theme/Admonition/Layout/styles.module.css +36 -0
- package/template/src/theme/Admonition/Type/Caution.js +28 -0
- package/template/src/theme/Admonition/Type/Danger.js +26 -0
- package/template/src/theme/Admonition/Type/Info.js +26 -0
- package/template/src/theme/Admonition/Type/Note.js +26 -0
- package/template/src/theme/Admonition/Type/Tip.js +26 -0
- package/template/src/theme/Admonition/Type/Warning.js +26 -0
- package/template/src/theme/Admonition/Types.js +27 -0
- package/template/src/theme/Admonition/index.js +18 -0
- package/template/src/theme/AnnouncementBar/CloseButton/index.js +20 -0
- package/template/src/theme/AnnouncementBar/CloseButton/styles.module.css +4 -0
- package/template/src/theme/AnnouncementBar/Content/index.js +17 -0
- package/template/src/theme/AnnouncementBar/Content/styles.module.css +10 -0
- package/template/src/theme/AnnouncementBar/index.js +33 -0
- package/template/src/theme/AnnouncementBar/styles.module.css +55 -0
- package/template/src/theme/BlogSidebar/Content/index.js +35 -0
- package/template/src/theme/BlogSidebar/Desktop/index.js +44 -0
- package/template/src/theme/BlogSidebar/Desktop/styles.module.css +60 -0
- package/template/src/theme/BlogSidebar/Mobile/index.js +38 -0
- package/template/src/theme/BlogSidebar/Mobile/styles.module.css +3 -0
- package/template/src/theme/BlogSidebar/index.js +15 -0
- package/template/src/theme/Details/index.js +23 -0
- package/template/src/theme/Details/styles.module.css +52 -0
- package/template/src/theme/DocBreadcrumbs/Items/Home/index.js +22 -0
- package/template/src/theme/DocBreadcrumbs/Items/Home/styles.module.css +7 -0
- package/template/src/theme/DocBreadcrumbs/StructuredData/index.js +15 -0
- package/template/src/theme/DocBreadcrumbs/index.js +75 -0
- package/template/src/theme/DocBreadcrumbs/styles.module.css +5 -0
- package/template/src/theme/DocCard/index.js +93 -0
- package/template/src/theme/DocCard/styles.module.css +53 -0
- package/template/src/theme/DocCardList/index.js +27 -0
- package/template/src/theme/DocCardList/styles.module.css +7 -0
- package/template/src/theme/DocItem/Content/index.js +121 -0
- package/template/src/theme/DocItem/Content/styles.module.css +96 -0
- package/template/src/theme/DocItem/Footer/index.js +43 -0
- package/template/src/theme/DocItem/Footer/styles.module.css +19 -0
- package/template/src/theme/DocItem/Layout/index.js +55 -0
- package/template/src/theme/DocItem/Layout/styles.module.css +14 -0
- package/template/src/theme/DocItem/Metadata/index.js +17 -0
- package/template/src/theme/DocItem/Paginator/index.js +17 -0
- package/template/src/theme/DocItem/TOC/Desktop/index.js +15 -0
- package/template/src/theme/DocItem/TOC/Mobile/index.js +17 -0
- package/template/src/theme/DocItem/TOC/Mobile/styles.module.css +12 -0
- package/template/src/theme/DocItem/index.js +19 -0
- package/template/src/theme/DocItem/styles.module.css +28 -0
- package/template/src/theme/DocRoot/Layout/Main/index.js +23 -0
- package/template/src/theme/DocRoot/Layout/Main/styles.module.css +31 -0
- package/template/src/theme/DocRoot/Layout/Sidebar/ExpandButton/index.js +28 -0
- package/template/src/theme/DocRoot/Layout/Sidebar/ExpandButton/styles.module.css +27 -0
- package/template/src/theme/DocRoot/Layout/Sidebar/index.js +70 -0
- package/template/src/theme/DocRoot/Layout/Sidebar/styles.module.css +32 -0
- package/template/src/theme/DocRoot/Layout/index.js +27 -0
- package/template/src/theme/DocRoot/Layout/styles.module.css +9 -0
- package/template/src/theme/DocRoot/index.js +25 -0
- package/template/src/theme/DocSidebar/Desktop/CollapseButton/index.js +28 -0
- package/template/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css +40 -0
- package/template/src/theme/DocSidebar/Desktop/Content/index.js +44 -0
- package/template/src/theme/DocSidebar/Desktop/Content/styles.module.css +16 -0
- package/template/src/theme/DocSidebar/Desktop/index.js +28 -0
- package/template/src/theme/DocSidebar/Desktop/styles.module.css +37 -0
- package/template/src/theme/DocSidebar/Mobile/index.js +39 -0
- package/template/src/theme/DocSidebar/index.js +18 -0
- package/template/src/theme/DocSidebarItem/Category/index.js +203 -0
- package/template/src/theme/DocSidebarItem/Html/index.js +20 -0
- package/template/src/theme/DocSidebarItem/Html/styles.module.css +6 -0
- package/template/src/theme/DocSidebarItem/Link/index.js +49 -0
- package/template/src/theme/DocSidebarItem/Link/styles.module.css +3 -0
- package/template/src/theme/DocSidebarItem/index.js +15 -0
- package/template/src/theme/EditMetaRow/index.js +25 -0
- package/template/src/theme/EditMetaRow/styles.module.css +11 -0
- package/template/src/theme/EditThisPage/index.js +29 -0
- package/template/src/theme/ErrorPageContent.js +34 -0
- package/template/src/theme/Footer/Copyright/index.js +11 -0
- package/template/src/theme/Footer/Layout/index.js +21 -0
- package/template/src/theme/Footer/LinkItem/index.js +26 -0
- package/template/src/theme/Footer/Links/MultiColumn/index.js +44 -0
- package/template/src/theme/Footer/Links/Simple/index.js +32 -0
- package/template/src/theme/Footer/Links/index.js +11 -0
- package/template/src/theme/Footer/Logo/index.js +35 -0
- package/template/src/theme/Footer/Logo/styles.module.css +9 -0
- package/template/src/theme/Footer/index.js +22 -0
- package/template/src/theme/MDXComponents/Heading.js +120 -0
- package/template/src/theme/MDXComponents/index.js +17 -0
- package/template/src/theme/MDXComponents/styles.module.css +110 -0
- package/template/src/theme/MDXContent/index.js +6 -0
- package/template/src/theme/NavbarItem/DropdownNavbarItem/Desktop/index.js +110 -0
- package/template/src/theme/NavbarItem/DropdownNavbarItem/Mobile/index.js +136 -0
- package/template/src/theme/NavbarItem/DropdownNavbarItem/Mobile/styles.module.css +3 -0
- package/template/src/theme/NavbarItem/DropdownNavbarItem/index.js +7 -0
- package/template/src/theme/NotFound/Content/index.js +46 -0
- package/template/src/theme/NotFound/index.js +19 -0
- package/template/src/theme/NotFound.js +49 -0
- package/template/src/theme/PaginatorNavLink/index.js +17 -0
- package/template/src/theme/TOC/index.js +19 -0
- package/template/src/theme/TOC/styles.module.css +16 -0
- package/template/src/theme/TOCItems/Tree.js +30 -0
- package/template/src/theme/TOCItems/index.js +47 -0
- package/template/src/theme/TabItem/index.js +15 -0
- package/template/src/theme/TabItem/styles.module.css +10 -0
- package/template/src/theme/Tabs/index.js +189 -0
- package/template/src/theme/Tabs/styles.module.css +74 -0
- package/template/static/img/favicon.svg +4 -0
- package/template/static/img/oops-404.svg +11 -0
- package/template/static/searchIndex.json +1 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// packages/redirects-plugin/index.js
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
|
|
6
|
+
module.exports = function(context, options) {
|
|
7
|
+
const {
|
|
8
|
+
siteConfig: { baseUrl, trailingSlash },
|
|
9
|
+
siteDir,
|
|
10
|
+
outDir,
|
|
11
|
+
} = context;
|
|
12
|
+
|
|
13
|
+
// Get redirects from various possible sources
|
|
14
|
+
let redirects = [];
|
|
15
|
+
|
|
16
|
+
// 1. Check for redirects in options (from docusaurus.config.js)
|
|
17
|
+
if (options.redirects && Array.isArray(options.redirects)) {
|
|
18
|
+
redirects = [...redirects, ...options.redirects];
|
|
19
|
+
console.log(`Loaded ${options.redirects.length} redirects from config`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 2. Check for redirects file specified in options
|
|
23
|
+
if (options.redirectsFile) {
|
|
24
|
+
const specifiedPath = path.resolve(siteDir, options.redirectsFile);
|
|
25
|
+
if (fs.existsSync(specifiedPath)) {
|
|
26
|
+
try {
|
|
27
|
+
const fileRedirects = JSON.parse(fs.readFileSync(specifiedPath, 'utf8'));
|
|
28
|
+
if (Array.isArray(fileRedirects)) {
|
|
29
|
+
redirects = [...redirects, ...fileRedirects];
|
|
30
|
+
console.log(`Loaded ${fileRedirects.length} redirects from ${options.redirectsFile}`);
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(`Error loading redirects from ${options.redirectsFile}: ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 3. Auto-detect redirects.json in common locations (if not specified in options)
|
|
39
|
+
if (!options.redirectsFile) {
|
|
40
|
+
const possibleLocations = [
|
|
41
|
+
path.resolve(siteDir, 'redirects.json'),
|
|
42
|
+
path.resolve(siteDir, 'config/redirects.json'),
|
|
43
|
+
path.resolve(siteDir, 'src/redirects.json')
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
for (const location of possibleLocations) {
|
|
47
|
+
if (fs.existsSync(location)) {
|
|
48
|
+
try {
|
|
49
|
+
const fileRedirects = JSON.parse(fs.readFileSync(location, 'utf8'));
|
|
50
|
+
if (Array.isArray(fileRedirects)) {
|
|
51
|
+
redirects = [...redirects, ...fileRedirects];
|
|
52
|
+
console.log(`Auto-detected and loaded ${fileRedirects.length} redirects from ${path.relative(siteDir, location)}`);
|
|
53
|
+
break; // Only use the first valid file found
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.warn(`Found redirects file at ${location} but couldn't parse it: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
name: 'redirects-plugin',
|
|
64
|
+
|
|
65
|
+
async loadContent() {
|
|
66
|
+
console.log('===== REDIRECTS PLUGIN: LOADING CONTENT =====');
|
|
67
|
+
console.log(`Found ${redirects.length} redirects to process`);
|
|
68
|
+
console.log(`Trailing slash setting: ${trailingSlash}`);
|
|
69
|
+
if (redirects.length > 0) {
|
|
70
|
+
console.log('First redirect:', JSON.stringify(redirects[0]));
|
|
71
|
+
}
|
|
72
|
+
return { redirects };
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
async postBuild({ content }) {
|
|
76
|
+
console.log('===== REDIRECTS PLUGIN: POST BUILD =====');
|
|
77
|
+
const { redirects } = content;
|
|
78
|
+
|
|
79
|
+
if (redirects.length === 0) {
|
|
80
|
+
console.log('No redirects configured. Skipping redirect generation.');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(`Processing ${redirects.length} redirects...`);
|
|
85
|
+
console.log(`Output directory: ${outDir}`);
|
|
86
|
+
console.log(`Trailing slash setting: ${trailingSlash}`);
|
|
87
|
+
|
|
88
|
+
// For each redirect, create an HTML file with meta refresh
|
|
89
|
+
await Promise.all(
|
|
90
|
+
redirects.map(async (redirect, index) => {
|
|
91
|
+
// Validate redirect object
|
|
92
|
+
if (!redirect.from || !redirect.to) {
|
|
93
|
+
console.warn(`Skipping invalid redirect at index ${index}: missing from or to field`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { from, to, type = 301 } = redirect;
|
|
98
|
+
|
|
99
|
+
// Separate the path from the hash fragment for 'from'
|
|
100
|
+
const [fromPath, fromHash] = from.split('#');
|
|
101
|
+
|
|
102
|
+
// Normalize the 'from' path - remove trailing slash for file system
|
|
103
|
+
const normalizedFrom = fromPath.startsWith('/')
|
|
104
|
+
? fromPath.endsWith('/') ? fromPath.slice(0, -1) : fromPath
|
|
105
|
+
: `/${fromPath.endsWith('/') ? fromPath.slice(0, -1) : fromPath}`;
|
|
106
|
+
|
|
107
|
+
// Create the output path (without hash since that's not a file system concept)
|
|
108
|
+
const outputPath = path.join(outDir, normalizedFrom.slice(1));
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Handle the 'to' URL with proper trailing slash handling
|
|
112
|
+
let toUrl;
|
|
113
|
+
|
|
114
|
+
if (to.startsWith('http')) {
|
|
115
|
+
// External URL - use as-is
|
|
116
|
+
toUrl = to;
|
|
117
|
+
} else {
|
|
118
|
+
// Internal URL - ensure proper format based on trailingSlash setting
|
|
119
|
+
let processedTo = to.startsWith('/') ? to : `/${to}`;
|
|
120
|
+
|
|
121
|
+
// Apply trailing slash rules
|
|
122
|
+
if (trailingSlash === true && !processedTo.endsWith('/') && !processedTo.includes('#') && !processedTo.includes('?')) {
|
|
123
|
+
processedTo = `${processedTo}/`;
|
|
124
|
+
} else if (trailingSlash === false && processedTo.endsWith('/')) {
|
|
125
|
+
processedTo = processedTo.slice(0, -1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Apply baseUrl
|
|
129
|
+
toUrl = baseUrl === '/' ? processedTo : `${baseUrl}${processedTo.slice(1)}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Create the HTML file
|
|
133
|
+
const htmlContent = `<!DOCTYPE html>
|
|
134
|
+
<html>
|
|
135
|
+
<head>
|
|
136
|
+
<meta charset="UTF-8">
|
|
137
|
+
<meta http-equiv="refresh" content="0; url=${toUrl}">
|
|
138
|
+
<link rel="canonical" href="${toUrl}" />
|
|
139
|
+
<title>Redirecting...</title>
|
|
140
|
+
</head>
|
|
141
|
+
<body>
|
|
142
|
+
<p>Redirecting to <a href="${toUrl}">${toUrl}</a>...</p>
|
|
143
|
+
<script>
|
|
144
|
+
window.location.href = '${toUrl}';
|
|
145
|
+
</script>
|
|
146
|
+
</body>
|
|
147
|
+
</html>`;
|
|
148
|
+
|
|
149
|
+
// Generate file-based redirect (path.html)
|
|
150
|
+
await fs.ensureFile(`${outputPath}.html`);
|
|
151
|
+
await fs.writeFile(`${outputPath}.html`, htmlContent);
|
|
152
|
+
|
|
153
|
+
// Also generate directory-based redirect (path/index.html)
|
|
154
|
+
await fs.ensureDir(outputPath);
|
|
155
|
+
await fs.writeFile(path.join(outputPath, 'index.html'), htmlContent);
|
|
156
|
+
|
|
157
|
+
console.log(`Created redirect: ${from} → ${toUrl} (${type})`);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error(`Error creating redirect for ${from}: ${error.message}`);
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
console.log('Redirect generation complete.');
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "redirects-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A custom Docusaurus plugin for handling redirects",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"author": "Patricia McPhee",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"@docusaurus/core": "3.8.1",
|
|
10
|
+
"react": "^19.0.0"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"fs-extra": "^11.1.1"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
2
|
+
# yarn lockfile v1
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
fs-extra@^11.1.1:
|
|
6
|
+
version "11.3.0"
|
|
7
|
+
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
|
|
8
|
+
integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
|
|
9
|
+
dependencies:
|
|
10
|
+
graceful-fs "^4.2.0"
|
|
11
|
+
jsonfile "^6.0.1"
|
|
12
|
+
universalify "^2.0.0"
|
|
13
|
+
|
|
14
|
+
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
|
15
|
+
version "4.2.11"
|
|
16
|
+
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
|
17
|
+
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
|
18
|
+
|
|
19
|
+
jsonfile@^6.0.1:
|
|
20
|
+
version "6.1.0"
|
|
21
|
+
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
|
22
|
+
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
|
|
23
|
+
dependencies:
|
|
24
|
+
universalify "^2.0.0"
|
|
25
|
+
optionalDependencies:
|
|
26
|
+
graceful-fs "^4.1.6"
|
|
27
|
+
|
|
28
|
+
universalify@^2.0.0:
|
|
29
|
+
version "2.0.1"
|
|
30
|
+
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
|
31
|
+
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// scripts/build-tokens.js
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
// Read your design tokens JSON
|
|
6
|
+
const tokens = require('../design-tokens.json');
|
|
7
|
+
|
|
8
|
+
// Function to convert nested JSON to CSS variables
|
|
9
|
+
function jsonToCss(obj, prefix = '--') {
|
|
10
|
+
let css = '';
|
|
11
|
+
|
|
12
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
13
|
+
const varName = prefix + key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
14
|
+
|
|
15
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
16
|
+
css += jsonToCss(value, varName + '-');
|
|
17
|
+
} else {
|
|
18
|
+
css += ` ${varName}: ${value};\n`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return css;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Generate CSS
|
|
26
|
+
const cssContent = `:root {\n${jsonToCss(tokens)}}\n`;
|
|
27
|
+
|
|
28
|
+
// Write to custom.css or a separate tokens.css file
|
|
29
|
+
fs.writeFileSync(
|
|
30
|
+
path.join(__dirname, '../src/css/tokens.css'),
|
|
31
|
+
cssContent
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
console.log('✅ Design tokens converted to CSS');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
|
2
|
+
const sidebars = {
|
|
3
|
+
mainSidebar: [
|
|
4
|
+
'getting-started',
|
|
5
|
+
{
|
|
6
|
+
type: 'category',
|
|
7
|
+
label: 'Guides',
|
|
8
|
+
collapsed: false,
|
|
9
|
+
collapsible: true,
|
|
10
|
+
items: [
|
|
11
|
+
'guides/writing-docs',
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports = sidebars;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// src/components/CustomSearch/CustomSearch.js
|
|
2
|
+
import React, { useState, useEffect } from 'react';
|
|
3
|
+
import { useHistory } from '@docusaurus/router';
|
|
4
|
+
import { Input, Modal, List } from 'antd';
|
|
5
|
+
import { SearchOutlined } from '@ant-design/icons';
|
|
6
|
+
import Fuse from 'fuse.js';
|
|
7
|
+
import styles from './styles.module.css';
|
|
8
|
+
|
|
9
|
+
const { Search } = Input;
|
|
10
|
+
|
|
11
|
+
const fuseOptions = {
|
|
12
|
+
includeScore: true,
|
|
13
|
+
threshold: 0.4,
|
|
14
|
+
minMatchCharLength: 2,
|
|
15
|
+
keys: [
|
|
16
|
+
{
|
|
17
|
+
name: 'keywords',
|
|
18
|
+
weight: 0.7
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'title',
|
|
22
|
+
weight: 0.5
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'description',
|
|
26
|
+
weight: 0.3
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function CustomSearch() {
|
|
32
|
+
const [searchIndex, setSearchIndex] = useState([]);
|
|
33
|
+
const [fuse, setFuse] = useState(null);
|
|
34
|
+
const [searchResults, setSearchResults] = useState([]);
|
|
35
|
+
const [loading, setLoading] = useState(true);
|
|
36
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
37
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
38
|
+
const history = useHistory();
|
|
39
|
+
|
|
40
|
+
const handleClear = () => {
|
|
41
|
+
setSearchTerm('');
|
|
42
|
+
setSearchResults([]);
|
|
43
|
+
setModalVisible(false);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleModalClose = () => {
|
|
47
|
+
handleClear();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
async function loadSearchIndex() {
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch('/searchIndex.json');
|
|
54
|
+
if (!response.ok) throw new Error(`Failed to load search index: ${response.statusText}`);
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
|
|
57
|
+
const processedData = data.map(item => ({
|
|
58
|
+
...item,
|
|
59
|
+
keywords: Array.isArray(item.keywords)
|
|
60
|
+
? item.keywords.map(k => k?.toLowerCase()?.trim()).filter(Boolean)
|
|
61
|
+
: [],
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
setSearchIndex(processedData);
|
|
65
|
+
setFuse(new Fuse(processedData, fuseOptions));
|
|
66
|
+
setLoading(false);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('Error loading search index:', error);
|
|
69
|
+
setLoading(false);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
loadSearchIndex();
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
const performSearch = (value) => {
|
|
77
|
+
if (!value.trim() || !fuse) {
|
|
78
|
+
setSearchResults([]);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const results = fuse.search(value.trim());
|
|
84
|
+
const processedResults = Array.from(
|
|
85
|
+
new Map(
|
|
86
|
+
results.map(result => [result.item.id, result.item])
|
|
87
|
+
).values()
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
setSearchResults(processedResults);
|
|
91
|
+
setModalVisible(true);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Search error:', error);
|
|
94
|
+
setSearchResults([]);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleSearchSubmit = (value) => {
|
|
99
|
+
performSearch(value);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleSearchChange = (e) => {
|
|
103
|
+
setSearchTerm(e.target.value);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const navigateToPage = (url) => {
|
|
107
|
+
setModalVisible(false);
|
|
108
|
+
setSearchTerm('');
|
|
109
|
+
history.push(url);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const formatDate = (lastUpdate) => {
|
|
113
|
+
if (!lastUpdate) return null;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// Handle both string and object formats
|
|
117
|
+
const dateStr = typeof lastUpdate === 'string'
|
|
118
|
+
? lastUpdate
|
|
119
|
+
: lastUpdate.date || lastUpdate.lastUpdatedAt;
|
|
120
|
+
|
|
121
|
+
if (!dateStr) return null;
|
|
122
|
+
|
|
123
|
+
// Parse the date string
|
|
124
|
+
const date = new Date(dateStr);
|
|
125
|
+
if (isNaN(date.getTime())) return null;
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
date: date.toLocaleDateString('en-US', {
|
|
129
|
+
year: 'numeric',
|
|
130
|
+
month: 'long',
|
|
131
|
+
day: 'numeric'
|
|
132
|
+
}),
|
|
133
|
+
author: typeof lastUpdate === 'object' ? lastUpdate.author : null
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error('Date formatting error:', error);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const renderLastUpdate = (item) => {
|
|
142
|
+
const lastUpdateData = item.last_update || item.lastUpdatedAt;
|
|
143
|
+
const formattedDate = formatDate(lastUpdateData);
|
|
144
|
+
|
|
145
|
+
if (!formattedDate) return null;
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div className={styles.searchResultMeta}>
|
|
149
|
+
<span className={styles.searchResultDate}>
|
|
150
|
+
Last updated: {formattedDate.date}
|
|
151
|
+
</span>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const renderTitle = (item) => (
|
|
157
|
+
<a
|
|
158
|
+
onClick={() => navigateToPage(item.url)}
|
|
159
|
+
className={styles.searchResultTitle}
|
|
160
|
+
role="button"
|
|
161
|
+
tabIndex={0}
|
|
162
|
+
onKeyPress={(e) => {
|
|
163
|
+
if (e.key === 'Enter') {
|
|
164
|
+
navigateToPage(item.url);
|
|
165
|
+
}
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
{item.title}
|
|
169
|
+
</a>
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div className={styles.searchWrapper}>
|
|
174
|
+
<Search
|
|
175
|
+
placeholder="Search documentation..."
|
|
176
|
+
onSearch={handleSearchSubmit}
|
|
177
|
+
value={searchTerm}
|
|
178
|
+
onChange={handleSearchChange}
|
|
179
|
+
className={styles.searchInput}
|
|
180
|
+
loading={loading}
|
|
181
|
+
prefix={<SearchOutlined />}
|
|
182
|
+
allowClear={true}
|
|
183
|
+
onClear={handleClear}
|
|
184
|
+
/>
|
|
185
|
+
|
|
186
|
+
<Modal
|
|
187
|
+
title={`Search Results${searchResults.length ? ` (${searchResults.length})` : ''}`}
|
|
188
|
+
open={modalVisible}
|
|
189
|
+
onCancel={handleModalClose}
|
|
190
|
+
footer={null}
|
|
191
|
+
width={600}
|
|
192
|
+
className={styles.searchModal}
|
|
193
|
+
destroyOnClose
|
|
194
|
+
>
|
|
195
|
+
<div className={styles.searchModalContent}>
|
|
196
|
+
{searchResults.length > 0 ? (
|
|
197
|
+
<List
|
|
198
|
+
className={styles.searchResultsList}
|
|
199
|
+
itemLayout="vertical"
|
|
200
|
+
size="small"
|
|
201
|
+
pagination={{
|
|
202
|
+
pageSize: 5,
|
|
203
|
+
className: styles.searchPagination,
|
|
204
|
+
size: "small"
|
|
205
|
+
}}
|
|
206
|
+
dataSource={searchResults}
|
|
207
|
+
renderItem={(item) => (
|
|
208
|
+
<List.Item
|
|
209
|
+
key={item.id}
|
|
210
|
+
className={styles.searchResultItem}
|
|
211
|
+
>
|
|
212
|
+
<List.Item.Meta
|
|
213
|
+
title={renderTitle(item)}
|
|
214
|
+
description={
|
|
215
|
+
<div className={styles.searchResultContent}>
|
|
216
|
+
<div className={styles.searchResultDescription}>
|
|
217
|
+
{item.description}
|
|
218
|
+
</div>
|
|
219
|
+
{renderLastUpdate(item)}
|
|
220
|
+
</div>
|
|
221
|
+
}
|
|
222
|
+
/>
|
|
223
|
+
</List.Item>
|
|
224
|
+
)}
|
|
225
|
+
/>
|
|
226
|
+
) : searchTerm ? (
|
|
227
|
+
<div className={styles.searchNoResults}>
|
|
228
|
+
No results found for "{searchTerm}"
|
|
229
|
+
</div>
|
|
230
|
+
) : (
|
|
231
|
+
<div className={styles.searchInstructions}>
|
|
232
|
+
Enter your search terms to find documentation
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
</Modal>
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export default CustomSearch;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// src/components/CustomSearch/CustomSearchContent.js
|
|
2
|
+
import React, { useState, useEffect } from 'react';
|
|
3
|
+
import { Input, List } from 'antd';
|
|
4
|
+
import { SearchOutlined } from '@ant-design/icons';
|
|
5
|
+
import { useHistory } from '@docusaurus/router';
|
|
6
|
+
import Fuse from 'fuse.js';
|
|
7
|
+
import styles from './styles.module.css';
|
|
8
|
+
|
|
9
|
+
const { Search } = Input;
|
|
10
|
+
|
|
11
|
+
const fuseOptions = {
|
|
12
|
+
includeScore: true,
|
|
13
|
+
threshold: 0.4,
|
|
14
|
+
minMatchCharLength: 2,
|
|
15
|
+
keys: [
|
|
16
|
+
{ name: 'keywords', weight: 0.7 },
|
|
17
|
+
{ name: 'title', weight: 0.5 },
|
|
18
|
+
{ name: 'description', weight: 0.3 }
|
|
19
|
+
]
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function CustomSearchContent({ onClose, initialSearchTerm = '' }) {
|
|
23
|
+
const [searchIndex, setSearchIndex] = useState([]);
|
|
24
|
+
const [fuse, setFuse] = useState(null);
|
|
25
|
+
const [searchResults, setSearchResults] = useState([]);
|
|
26
|
+
const [loading, setLoading] = useState(true);
|
|
27
|
+
const [searchTerm, setSearchTerm] = useState(initialSearchTerm);
|
|
28
|
+
const history = useHistory();
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
loadSearchIndex();
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
const loadSearchIndex = async () => {
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch('/searchIndex.json');
|
|
37
|
+
if (!response.ok) throw new Error('Failed to load search index');
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
|
|
40
|
+
const processedData = data.map(item => ({
|
|
41
|
+
...item,
|
|
42
|
+
keywords: Array.isArray(item.keywords)
|
|
43
|
+
? item.keywords.map(k => k?.toLowerCase()?.trim()).filter(Boolean)
|
|
44
|
+
: [],
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
setSearchIndex(processedData);
|
|
48
|
+
setFuse(new Fuse(processedData, fuseOptions));
|
|
49
|
+
setLoading(false);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Error loading search index:', error);
|
|
52
|
+
setLoading(false);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const performSearch = (value) => {
|
|
57
|
+
if (!value?.trim() || !fuse) {
|
|
58
|
+
setSearchResults([]);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const results = fuse.search(value.trim());
|
|
64
|
+
const processedResults = Array.from(
|
|
65
|
+
new Map(results.map(result => [result.item.id, result.item])).values()
|
|
66
|
+
);
|
|
67
|
+
setSearchResults(processedResults);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Search error:', error);
|
|
70
|
+
setSearchResults([]);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const handleSearchSubmit = (value) => {
|
|
75
|
+
performSearch(value);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleSearchChange = (e) => {
|
|
79
|
+
setSearchTerm(e.target.value);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const navigateToPage = (url) => {
|
|
83
|
+
onClose();
|
|
84
|
+
history.push(url);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const formatDate = (lastUpdate) => {
|
|
88
|
+
if (!lastUpdate?.date) return null;
|
|
89
|
+
try {
|
|
90
|
+
const [month, day, year] = lastUpdate.date.split('/').map(num => parseInt(num, 10));
|
|
91
|
+
const date = new Date(year, month - 1, day);
|
|
92
|
+
return isNaN(date.getTime()) ? null : {
|
|
93
|
+
date: date.toLocaleDateString('en-US', {
|
|
94
|
+
year: 'numeric',
|
|
95
|
+
month: 'long',
|
|
96
|
+
day: 'numeric'
|
|
97
|
+
})
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div className={styles.searchModalContent}>
|
|
106
|
+
<Search
|
|
107
|
+
placeholder="Search documentation..."
|
|
108
|
+
onSearch={handleSearchSubmit}
|
|
109
|
+
value={searchTerm}
|
|
110
|
+
onChange={handleSearchChange}
|
|
111
|
+
className={styles.searchInput}
|
|
112
|
+
loading={loading}
|
|
113
|
+
prefix={<SearchOutlined />}
|
|
114
|
+
allowClear
|
|
115
|
+
/>
|
|
116
|
+
|
|
117
|
+
<div className={styles.searchResults}>
|
|
118
|
+
{searchResults.length > 0 ? (
|
|
119
|
+
<List
|
|
120
|
+
className={styles.searchResultsList}
|
|
121
|
+
itemLayout="vertical"
|
|
122
|
+
size="small"
|
|
123
|
+
pagination={{
|
|
124
|
+
pageSize: 5,
|
|
125
|
+
size: "small"
|
|
126
|
+
}}
|
|
127
|
+
dataSource={searchResults}
|
|
128
|
+
renderItem={(item) => (
|
|
129
|
+
<List.Item key={item.id} className={styles.searchResultItem}>
|
|
130
|
+
<List.Item.Meta
|
|
131
|
+
title={
|
|
132
|
+
<a
|
|
133
|
+
onClick={() => navigateToPage(item.url)}
|
|
134
|
+
className={styles.searchResultTitle}
|
|
135
|
+
role="button"
|
|
136
|
+
tabIndex={0}
|
|
137
|
+
>
|
|
138
|
+
{item.title}
|
|
139
|
+
</a>
|
|
140
|
+
}
|
|
141
|
+
description={
|
|
142
|
+
<>
|
|
143
|
+
<div className={styles.searchResultDescription}>
|
|
144
|
+
{item.description}
|
|
145
|
+
</div>
|
|
146
|
+
{formatDate(item.last_update) && (
|
|
147
|
+
<div className={styles.searchResultDate}>
|
|
148
|
+
Last updated: {formatDate(item.last_update).date}
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
</>
|
|
152
|
+
}
|
|
153
|
+
/>
|
|
154
|
+
</List.Item>
|
|
155
|
+
)}
|
|
156
|
+
/>
|
|
157
|
+
) : searchTerm ? (
|
|
158
|
+
<div className={styles.searchNoResults}>
|
|
159
|
+
No results found for "{searchTerm}"
|
|
160
|
+
</div>
|
|
161
|
+
) : (
|
|
162
|
+
<div className={styles.searchInstructions}>
|
|
163
|
+
Enter your search terms to find documentation
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default CustomSearchContent;
|