@marlinjai/clearify 1.8.0 → 1.10.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/README.md +3 -3
- package/dist/node/{chunk-3WWPYCGQ.js → chunk-2RAA6477.js} +3 -3
- package/dist/node/chunk-AJBCHKBP.js +105 -0
- package/dist/node/{chunk-4BOXMNVR.js → chunk-ELRDUSZS.js} +3 -3
- package/dist/node/{chunk-YMRRZEYA.js → chunk-PVTUVUBK.js} +105 -29
- package/dist/node/{chunk-6PUBRHS7.js → chunk-QNC5QRIO.js} +55 -18
- package/dist/node/{chunk-W3MZOFJQ.js → chunk-ZNURJXMI.js} +4 -4
- package/dist/node/core/config.d.ts +1 -1
- package/dist/node/core/config.js +2 -1
- package/dist/node/core/navigation.js +1 -1
- package/dist/node/core/remote.d.ts +18 -0
- package/dist/node/core/remote.js +8 -0
- package/dist/node/node/build.js +5 -4
- package/dist/node/node/check.js +4 -3
- package/dist/node/node/index.js +6 -5
- package/dist/node/types/index.d.ts +15 -1
- package/dist/node/vite-plugin/index.js +4 -3
- package/package.json +8 -7
- package/src/theme/Layout.tsx +1 -0
- package/src/theme/Sidebar.tsx +34 -2
- package/src/theme/components/ProjectGrid.tsx +121 -31
package/README.md
CHANGED
|
@@ -20,19 +20,19 @@ An open-source documentation site generator. Turn markdown into beautiful docs.
|
|
|
20
20
|
Install Clearify as a dev dependency:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
|
|
23
|
+
pnpm add -D @marlinjai/clearify
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Scaffold your project:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
|
|
29
|
+
pnpm exec clearify init
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
Start the dev server:
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
|
|
35
|
+
pnpm exec clearify dev
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
Your docs are live at `http://localhost:4747`.
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
resolveConfig,
|
|
7
7
|
resolveSections,
|
|
8
8
|
scanHubProjects
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-PVTUVUBK.js";
|
|
10
10
|
import {
|
|
11
11
|
buildSectionData
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-QNC5QRIO.js";
|
|
13
13
|
|
|
14
14
|
// src/vite-plugin/index.ts
|
|
15
15
|
import { resolve } from "path";
|
|
@@ -222,7 +222,7 @@ ${routeEntries.join(",\n")}
|
|
|
222
222
|
const userConfig = await loadUserConfig(userRoot);
|
|
223
223
|
config = resolveConfig(userConfig, userRoot);
|
|
224
224
|
config = await scanHubProjects(config, userRoot);
|
|
225
|
-
const allSections = resolveSections(config, userRoot);
|
|
225
|
+
const allSections = await resolveSections(config, userRoot);
|
|
226
226
|
const isDev = resolvedViteConfig.command === "serve";
|
|
227
227
|
visibleSections = isDev ? allSections : allSections.filter((s) => !s.draft);
|
|
228
228
|
await refreshDocs();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// src/core/remote.ts
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { existsSync, mkdirSync } from "fs";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
function repoSlug(repo, ref) {
|
|
6
|
+
return repo.replace(/^(https?:\/\/|git@)/, "").replace(/\.git$/, "").replace(/[/:@]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") + "--" + ref;
|
|
7
|
+
}
|
|
8
|
+
function isPinnedRef(ref) {
|
|
9
|
+
return /^[0-9a-f]{40}$/i.test(ref) || /^v?\d+\.\d+/.test(ref);
|
|
10
|
+
}
|
|
11
|
+
async function resolveRemoteSource(source, cacheDir) {
|
|
12
|
+
const ref = source.ref ?? "main";
|
|
13
|
+
const slug = repoSlug(source.repo, ref);
|
|
14
|
+
const localPath = resolve(cacheDir, slug);
|
|
15
|
+
const sparse = source.sparse !== false;
|
|
16
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
17
|
+
if (existsSync(resolve(localPath, ".git"))) {
|
|
18
|
+
if (!isPinnedRef(ref)) {
|
|
19
|
+
try {
|
|
20
|
+
execSync(`git -C ${JSON.stringify(localPath)} fetch origin ${ref} --depth 1`, {
|
|
21
|
+
stdio: "pipe",
|
|
22
|
+
timeout: 3e4
|
|
23
|
+
});
|
|
24
|
+
execSync(`git -C ${JSON.stringify(localPath)} checkout FETCH_HEAD`, {
|
|
25
|
+
stdio: "pipe"
|
|
26
|
+
});
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.warn(
|
|
29
|
+
` \u26A0 Failed to update remote "${source.repo}" (ref: ${ref}), using stale cache.`,
|
|
30
|
+
err instanceof Error ? err.message : ""
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const commit2 = getHeadCommit(localPath);
|
|
35
|
+
return { localPath, resolvedCommit: commit2, cached: true };
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
if (sparse && source.path) {
|
|
39
|
+
execSync(
|
|
40
|
+
`git clone --no-checkout --depth 1 --branch ${ref} ${source.repo} ${JSON.stringify(localPath)}`,
|
|
41
|
+
{ stdio: "pipe", timeout: 6e4 }
|
|
42
|
+
);
|
|
43
|
+
execSync(`git -C ${JSON.stringify(localPath)} sparse-checkout init --cone`, {
|
|
44
|
+
stdio: "pipe"
|
|
45
|
+
});
|
|
46
|
+
execSync(`git -C ${JSON.stringify(localPath)} sparse-checkout set ${source.path}`, {
|
|
47
|
+
stdio: "pipe"
|
|
48
|
+
});
|
|
49
|
+
execSync(`git -C ${JSON.stringify(localPath)} checkout`, { stdio: "pipe" });
|
|
50
|
+
} else {
|
|
51
|
+
execSync(
|
|
52
|
+
`git clone --depth 1 --branch ${ref} ${source.repo} ${JSON.stringify(localPath)}`,
|
|
53
|
+
{ stdio: "pipe", timeout: 6e4 }
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Failed to clone remote repo "${source.repo}" (ref: ${ref}): ${msg}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const commit = getHeadCommit(localPath);
|
|
63
|
+
return { localPath, resolvedCommit: commit, cached: false };
|
|
64
|
+
}
|
|
65
|
+
function getHeadCommit(repoPath) {
|
|
66
|
+
try {
|
|
67
|
+
return execSync(`git -C ${JSON.stringify(repoPath)} rev-parse HEAD`, {
|
|
68
|
+
encoding: "utf-8",
|
|
69
|
+
stdio: "pipe"
|
|
70
|
+
}).trim();
|
|
71
|
+
} catch {
|
|
72
|
+
return "unknown";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function resolveRemoteSections(sections, cacheDir) {
|
|
76
|
+
const results = [];
|
|
77
|
+
for (const section of sections) {
|
|
78
|
+
if (!section.git) {
|
|
79
|
+
results.push(section);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const { localPath, cached } = await resolveRemoteSource(section.git, cacheDir);
|
|
83
|
+
const absoluteDocsDir = resolve(localPath, section.docsDir);
|
|
84
|
+
if (!existsSync(absoluteDocsDir)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Remote section "${section.label}": docs directory "${section.docsDir}" not found in cloned repo "${section.git.repo}".`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
console.log(
|
|
90
|
+
` Remote section "${section.label}": ${cached ? "cached" : "cloned"} \u2192 ${absoluteDocsDir}`
|
|
91
|
+
);
|
|
92
|
+
results.push({
|
|
93
|
+
...section,
|
|
94
|
+
docsDir: absoluteDocsDir,
|
|
95
|
+
git: void 0
|
|
96
|
+
// Already resolved — docsDir is now absolute
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return results;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
resolveRemoteSource,
|
|
104
|
+
resolveRemoteSections
|
|
105
|
+
};
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
loadUserConfig,
|
|
3
3
|
resolveConfig,
|
|
4
4
|
resolveSections
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PVTUVUBK.js";
|
|
6
6
|
import {
|
|
7
7
|
scanDocs
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-QNC5QRIO.js";
|
|
9
9
|
|
|
10
10
|
// src/node/check.ts
|
|
11
11
|
import { resolve } from "path";
|
|
@@ -14,7 +14,7 @@ async function checkLinks() {
|
|
|
14
14
|
const userRoot = process.cwd();
|
|
15
15
|
const userConfig = await loadUserConfig(userRoot);
|
|
16
16
|
const config = resolveConfig(userConfig, userRoot);
|
|
17
|
-
const sections = resolveSections(config, userRoot);
|
|
17
|
+
const sections = await resolveSections(config, userRoot);
|
|
18
18
|
const validRoutes = /* @__PURE__ */ new Set();
|
|
19
19
|
const allDocs = [];
|
|
20
20
|
for (const section of sections) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveRemoteSections
|
|
3
|
+
} from "./chunk-AJBCHKBP.js";
|
|
4
|
+
|
|
1
5
|
// src/core/config.ts
|
|
2
6
|
import { z } from "zod";
|
|
3
7
|
import { resolve, basename, dirname } from "path";
|
|
@@ -15,13 +19,20 @@ var NavigationItemSchema = z.lazy(
|
|
|
15
19
|
children: z.array(NavigationItemSchema).optional()
|
|
16
20
|
})
|
|
17
21
|
);
|
|
22
|
+
var RemoteGitSourceSchema = z.object({
|
|
23
|
+
repo: z.string(),
|
|
24
|
+
ref: z.string().optional(),
|
|
25
|
+
path: z.string().optional(),
|
|
26
|
+
sparse: z.boolean().optional()
|
|
27
|
+
});
|
|
18
28
|
var SectionConfigSchema = z.object({
|
|
19
29
|
label: z.string(),
|
|
20
30
|
docsDir: z.string(),
|
|
21
31
|
basePath: z.string().optional(),
|
|
22
32
|
draft: z.boolean().optional(),
|
|
23
33
|
sitemap: z.boolean().optional(),
|
|
24
|
-
exclude: z.array(z.string()).optional()
|
|
34
|
+
exclude: z.array(z.string()).optional(),
|
|
35
|
+
git: RemoteGitSourceSchema.optional()
|
|
25
36
|
});
|
|
26
37
|
var MermaidConfigSchema = z.object({
|
|
27
38
|
strategy: z.enum(["client", "build"]).default("client")
|
|
@@ -38,7 +49,15 @@ var HubProjectSchema = z.object({
|
|
|
38
49
|
repo: z.string().optional(),
|
|
39
50
|
status: z.enum(["active", "beta", "planned", "deprecated"]).default("active"),
|
|
40
51
|
icon: z.string().optional(),
|
|
41
|
-
tags: z.array(z.string()).optional()
|
|
52
|
+
tags: z.array(z.string()).optional(),
|
|
53
|
+
group: z.string().optional(),
|
|
54
|
+
mode: z.enum(["link", "embed"]).optional(),
|
|
55
|
+
git: RemoteGitSourceSchema.optional(),
|
|
56
|
+
embedSections: z.union([
|
|
57
|
+
z.literal("all"),
|
|
58
|
+
z.literal("public"),
|
|
59
|
+
z.array(z.string())
|
|
60
|
+
]).optional()
|
|
42
61
|
});
|
|
43
62
|
var HubProjectPartialSchema = z.object({
|
|
44
63
|
description: z.string(),
|
|
@@ -46,11 +65,15 @@ var HubProjectPartialSchema = z.object({
|
|
|
46
65
|
repo: z.string().optional(),
|
|
47
66
|
status: z.enum(["active", "beta", "planned", "deprecated"]).default("active"),
|
|
48
67
|
icon: z.string().optional(),
|
|
49
|
-
tags: z.array(z.string()).optional()
|
|
68
|
+
tags: z.array(z.string()).optional(),
|
|
69
|
+
group: z.string().optional(),
|
|
70
|
+
hubUrl: z.string().optional(),
|
|
71
|
+
hubName: z.string().optional()
|
|
50
72
|
}).optional();
|
|
51
73
|
var HubConfigSchema = z.object({
|
|
52
74
|
projects: z.array(HubProjectSchema).default([]),
|
|
53
|
-
scan: z.string().optional()
|
|
75
|
+
scan: z.string().optional(),
|
|
76
|
+
cacheDir: z.string().optional()
|
|
54
77
|
}).optional();
|
|
55
78
|
var ClearifyConfigSchema = z.object({
|
|
56
79
|
name: z.string().default("Documentation"),
|
|
@@ -178,18 +201,25 @@ function resolveConfig(userConfig = {}, root) {
|
|
|
178
201
|
function slugify(str) {
|
|
179
202
|
return str.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
180
203
|
}
|
|
181
|
-
function resolveSections(config, root) {
|
|
204
|
+
async function resolveSections(config, root) {
|
|
182
205
|
if (config.sections && config.sections.length > 0) {
|
|
183
|
-
const
|
|
206
|
+
const hasRemote = config.sections.some((s) => s.git);
|
|
207
|
+
let resolvedInputs = config.sections;
|
|
208
|
+
if (hasRemote) {
|
|
209
|
+
const cacheDir = resolve(root, "node_modules/.cache/clearify-remote");
|
|
210
|
+
resolvedInputs = await resolveRemoteSections(config.sections, cacheDir);
|
|
211
|
+
}
|
|
212
|
+
const sections = resolvedInputs.map((s, i) => {
|
|
184
213
|
const id = slugify(s.label);
|
|
185
214
|
const draft = s.draft ?? false;
|
|
186
215
|
const basePath = s.basePath ?? (i === 0 ? "/" : "/" + id);
|
|
187
216
|
const sitemap = s.sitemap ?? !draft;
|
|
188
217
|
const exclude = [...config.exclude ?? [], ...s.exclude ?? []];
|
|
218
|
+
const docsDir = s.docsDir.startsWith("/") ? s.docsDir : resolve(root, s.docsDir);
|
|
189
219
|
return {
|
|
190
220
|
id,
|
|
191
221
|
label: s.label,
|
|
192
|
-
docsDir
|
|
222
|
+
docsDir,
|
|
193
223
|
basePath,
|
|
194
224
|
draft,
|
|
195
225
|
sitemap,
|
|
@@ -218,36 +248,82 @@ function resolveSections(config, root) {
|
|
|
218
248
|
];
|
|
219
249
|
}
|
|
220
250
|
async function scanHubProjects(config, root) {
|
|
221
|
-
if (!config.hub?.scan)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const configPaths = globbySync(pattern, { cwd: root, absolute: true });
|
|
251
|
+
if (!config.hub?.scan && !config.hub?.projects?.some((p) => p.mode === "embed")) {
|
|
252
|
+
return config;
|
|
253
|
+
}
|
|
225
254
|
const scannedProjects = [];
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
255
|
+
if (config.hub?.scan) {
|
|
256
|
+
const { globbySync } = await import("globby");
|
|
257
|
+
const pattern = config.hub.scan;
|
|
258
|
+
const configPaths = globbySync(pattern, { cwd: root, absolute: true });
|
|
259
|
+
for (const configPath of configPaths) {
|
|
260
|
+
try {
|
|
261
|
+
const childConfig = await loadUserConfig(dirname(configPath));
|
|
262
|
+
if (!childConfig.hubProject) continue;
|
|
263
|
+
const name = childConfig.name ?? basename(dirname(configPath));
|
|
264
|
+
const hp = childConfig.hubProject;
|
|
265
|
+
scannedProjects.push({
|
|
266
|
+
name,
|
|
267
|
+
description: hp.description,
|
|
268
|
+
href: hp.href ?? childConfig.siteUrl,
|
|
269
|
+
repo: hp.repo,
|
|
270
|
+
status: hp.status,
|
|
271
|
+
icon: hp.icon,
|
|
272
|
+
tags: hp.tags,
|
|
273
|
+
group: hp.group
|
|
274
|
+
});
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
242
277
|
}
|
|
243
278
|
}
|
|
244
|
-
const manualNames = new Set((config.hub
|
|
279
|
+
const manualNames = new Set((config.hub?.projects ?? []).map((p) => p.name));
|
|
245
280
|
const merged = [
|
|
246
281
|
...scannedProjects.filter((p) => !manualNames.has(p.name)),
|
|
247
|
-
...config.hub
|
|
282
|
+
...config.hub?.projects ?? []
|
|
248
283
|
];
|
|
284
|
+
const embedSections = [];
|
|
285
|
+
for (const project of merged) {
|
|
286
|
+
if (project.mode !== "embed" || !project.git) continue;
|
|
287
|
+
const cacheDir = config.hub?.cacheDir ? resolve(root, config.hub.cacheDir) : resolve(root, "node_modules/.cache/clearify-remote");
|
|
288
|
+
try {
|
|
289
|
+
const { resolveRemoteSource } = await import("./core/remote.js");
|
|
290
|
+
const { localPath } = await resolveRemoteSource(project.git, cacheDir);
|
|
291
|
+
const remoteConfig = await loadUserConfig(localPath);
|
|
292
|
+
const remoteSections = remoteConfig.sections ?? [
|
|
293
|
+
{ label: remoteConfig.name ?? project.name, docsDir: remoteConfig.docsDir ?? "./docs" }
|
|
294
|
+
];
|
|
295
|
+
const filter = project.embedSections ?? "public";
|
|
296
|
+
const filtered = remoteSections.filter((s) => {
|
|
297
|
+
if (filter === "all") return true;
|
|
298
|
+
if (filter === "public") return !s.draft;
|
|
299
|
+
if (Array.isArray(filter)) return filter.includes(s.label);
|
|
300
|
+
return true;
|
|
301
|
+
});
|
|
302
|
+
for (const s of filtered) {
|
|
303
|
+
const docsDir = s.docsDir.startsWith("/") ? s.docsDir : resolve(localPath, s.docsDir);
|
|
304
|
+
const label = s.label === "Documentation" ? project.name : s.label;
|
|
305
|
+
const id = slugify(label);
|
|
306
|
+
embedSections.push({
|
|
307
|
+
label,
|
|
308
|
+
docsDir,
|
|
309
|
+
basePath: s.basePath ?? "/" + id,
|
|
310
|
+
draft: s.draft,
|
|
311
|
+
sitemap: s.sitemap,
|
|
312
|
+
exclude: s.exclude
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
console.log(` Hub embed "${project.name}": ${filtered.length} sections pulled`);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
console.warn(
|
|
318
|
+
` \u26A0 Hub embed "${project.name}" failed:`,
|
|
319
|
+
err instanceof Error ? err.message : err
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const finalSections = [...config.sections ?? [], ...embedSections];
|
|
249
324
|
return {
|
|
250
325
|
...config,
|
|
326
|
+
...embedSections.length > 0 ? { sections: finalSections } : {},
|
|
251
327
|
hub: {
|
|
252
328
|
...config.hub,
|
|
253
329
|
projects: merged
|
|
@@ -47,9 +47,20 @@ function scanDocs(docsDir, exclude = [], basePath = "/") {
|
|
|
47
47
|
}
|
|
48
48
|
function buildNavigation(docs, basePath = "/") {
|
|
49
49
|
const rootDir = basePath === "/" ? "/" : basePath.replace(/\/$/, "");
|
|
50
|
-
const
|
|
50
|
+
const indexPages = /* @__PURE__ */ new Map();
|
|
51
|
+
const nonIndexDocs = [];
|
|
51
52
|
for (const doc of docs) {
|
|
52
53
|
if (doc.routePath === rootDir || doc.routePath === "/") continue;
|
|
54
|
+
const parentDir = dirname(doc.routePath);
|
|
55
|
+
const fileName = basename(doc.filePath, extname(doc.filePath));
|
|
56
|
+
if (fileName === "index") {
|
|
57
|
+
indexPages.set(doc.routePath, doc);
|
|
58
|
+
} else {
|
|
59
|
+
nonIndexDocs.push(doc);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const groups = /* @__PURE__ */ new Map();
|
|
63
|
+
for (const doc of nonIndexDocs) {
|
|
53
64
|
const dir = dirname(doc.routePath);
|
|
54
65
|
if (!groups.has(dir)) groups.set(dir, []);
|
|
55
66
|
groups.get(dir).push(doc);
|
|
@@ -62,25 +73,51 @@ function buildNavigation(docs, basePath = "/") {
|
|
|
62
73
|
return a.routePath.localeCompare(b.routePath);
|
|
63
74
|
});
|
|
64
75
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
function buildGroupChildren(parentDir) {
|
|
77
|
+
const items = [];
|
|
78
|
+
const directFiles = groups.get(parentDir) ?? [];
|
|
79
|
+
for (const doc of directFiles) {
|
|
80
|
+
const item = { label: doc.frontmatter.title, path: doc.routePath };
|
|
81
|
+
if (doc.frontmatter.icon) item.icon = doc.frontmatter.icon;
|
|
82
|
+
items.push(item);
|
|
83
|
+
}
|
|
84
|
+
const childDirs = /* @__PURE__ */ new Set();
|
|
85
|
+
for (const dir of groups.keys()) {
|
|
86
|
+
if (dir !== parentDir && dirname(dir) === parentDir) {
|
|
87
|
+
childDirs.add(dir);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const indexPath of indexPages.keys()) {
|
|
91
|
+
const dir = indexPath;
|
|
92
|
+
if (dirname(dir) === parentDir && !childDirs.has(dir)) {
|
|
93
|
+
childDirs.add(dir);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const sortedChildDirs = [...childDirs].sort((a, b) => {
|
|
97
|
+
const indexA = indexPages.get(a);
|
|
98
|
+
const indexB = indexPages.get(b);
|
|
99
|
+
const orderA = indexA?.frontmatter.order ?? 999;
|
|
100
|
+
const orderB = indexB?.frontmatter.order ?? 999;
|
|
101
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
102
|
+
return a.localeCompare(b);
|
|
80
103
|
});
|
|
81
|
-
|
|
104
|
+
for (const childDir of sortedChildDirs) {
|
|
105
|
+
const indexPage = indexPages.get(childDir);
|
|
106
|
+
const label = indexPage?.frontmatter.title ?? toTitleCase(basename(childDir));
|
|
107
|
+
const children = buildGroupChildren(childDir);
|
|
108
|
+
if (children.length > 0) {
|
|
109
|
+
const group = { label, children };
|
|
110
|
+
if (indexPage?.frontmatter.icon) group.icon = indexPage.frontmatter.icon;
|
|
111
|
+
items.push(group);
|
|
112
|
+
} else if (indexPage) {
|
|
113
|
+
const item = { label, path: indexPage.routePath };
|
|
114
|
+
if (indexPage.frontmatter.icon) item.icon = indexPage.frontmatter.icon;
|
|
115
|
+
items.push(item);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return items;
|
|
82
119
|
}
|
|
83
|
-
return
|
|
120
|
+
return buildGroupChildren(rootDir);
|
|
84
121
|
}
|
|
85
122
|
function buildRoutes(docs, sectionId) {
|
|
86
123
|
return docs.map((doc) => ({
|
|
@@ -8,15 +8,15 @@ import {
|
|
|
8
8
|
} from "./chunk-SCZZB7OE.js";
|
|
9
9
|
import {
|
|
10
10
|
clearifyPlugin
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-2RAA6477.js";
|
|
12
12
|
import {
|
|
13
13
|
loadUserConfig,
|
|
14
14
|
resolveConfig,
|
|
15
15
|
resolveSections
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-PVTUVUBK.js";
|
|
17
17
|
import {
|
|
18
18
|
buildSectionData
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-QNC5QRIO.js";
|
|
20
20
|
|
|
21
21
|
// src/node/build.ts
|
|
22
22
|
import { build as viteBuild } from "vite";
|
|
@@ -140,7 +140,7 @@ async function buildSite() {
|
|
|
140
140
|
const config = resolveConfig(userConfig);
|
|
141
141
|
const outDir = resolve(userRoot, config.outDir);
|
|
142
142
|
const ssrOutDir = resolve(outDir, ".ssr");
|
|
143
|
-
const allSections = resolveSections(config, userRoot);
|
|
143
|
+
const allSections = await resolveSections(config, userRoot);
|
|
144
144
|
const productionSections = allSections.filter((s) => !s.draft);
|
|
145
145
|
const changelogPath = resolve(userRoot, "CHANGELOG.md");
|
|
146
146
|
const sectionDataList = productionSections.map(
|
|
@@ -4,7 +4,7 @@ export { defineConfig } from '../types/index.js';
|
|
|
4
4
|
declare const defaultConfig: ClearifyConfig;
|
|
5
5
|
declare function loadUserConfig(root: string): Promise<Partial<ClearifyConfig>>;
|
|
6
6
|
declare function resolveConfig(userConfig?: Partial<ClearifyConfig>, root?: string): ClearifyConfig;
|
|
7
|
-
declare function resolveSections(config: ClearifyConfig, root: string): ResolvedSection[]
|
|
7
|
+
declare function resolveSections(config: ClearifyConfig, root: string): Promise<ResolvedSection[]>;
|
|
8
8
|
declare function scanHubProjects(config: ClearifyConfig, root: string): Promise<ClearifyConfig>;
|
|
9
9
|
|
|
10
10
|
export { defaultConfig, loadUserConfig, resolveConfig, resolveSections, scanHubProjects };
|
package/dist/node/core/config.js
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SectionConfig, RemoteGitSource } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
interface ResolvedRemote {
|
|
4
|
+
localPath: string;
|
|
5
|
+
resolvedCommit: string;
|
|
6
|
+
cached: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Clone or update a git repo into the cache directory.
|
|
10
|
+
*/
|
|
11
|
+
declare function resolveRemoteSource(source: RemoteGitSource, cacheDir: string): Promise<ResolvedRemote>;
|
|
12
|
+
/**
|
|
13
|
+
* For each section with a `git` source, clone/update the repo and rewrite
|
|
14
|
+
* `docsDir` to the absolute path inside the cached clone.
|
|
15
|
+
*/
|
|
16
|
+
declare function resolveRemoteSections(sections: SectionConfig[], cacheDir: string): Promise<SectionConfig[]>;
|
|
17
|
+
|
|
18
|
+
export { resolveRemoteSections, resolveRemoteSource };
|
package/dist/node/node/build.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildSite
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-ZNURJXMI.js";
|
|
4
4
|
import "../chunk-PRTER35L.js";
|
|
5
5
|
import "../chunk-SCZZB7OE.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-2RAA6477.js";
|
|
7
7
|
import "../chunk-B2Q23JW3.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-PVTUVUBK.js";
|
|
9
|
+
import "../chunk-AJBCHKBP.js";
|
|
9
10
|
import "../chunk-V7LLYIRO.js";
|
|
10
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-QNC5QRIO.js";
|
|
11
12
|
import "../chunk-5TD7NQIW.js";
|
|
12
13
|
export {
|
|
13
14
|
buildSite
|
package/dist/node/node/check.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
checkLinks
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-ELRDUSZS.js";
|
|
4
|
+
import "../chunk-PVTUVUBK.js";
|
|
5
|
+
import "../chunk-AJBCHKBP.js";
|
|
5
6
|
import "../chunk-V7LLYIRO.js";
|
|
6
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-QNC5QRIO.js";
|
|
7
8
|
import "../chunk-5TD7NQIW.js";
|
|
8
9
|
export {
|
|
9
10
|
checkLinks
|
package/dist/node/node/index.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildSite
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-ZNURJXMI.js";
|
|
4
4
|
import {
|
|
5
5
|
remarkMermaidToComponent
|
|
6
6
|
} from "../chunk-PRTER35L.js";
|
|
7
7
|
import "../chunk-SCZZB7OE.js";
|
|
8
8
|
import {
|
|
9
9
|
clearifyPlugin
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-2RAA6477.js";
|
|
11
11
|
import "../chunk-B2Q23JW3.js";
|
|
12
12
|
import {
|
|
13
13
|
init
|
|
14
14
|
} from "../chunk-QIAMD2Q6.js";
|
|
15
15
|
import {
|
|
16
16
|
checkLinks
|
|
17
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-ELRDUSZS.js";
|
|
18
18
|
import {
|
|
19
19
|
loadUserConfig,
|
|
20
20
|
resolveConfig
|
|
21
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-PVTUVUBK.js";
|
|
22
|
+
import "../chunk-AJBCHKBP.js";
|
|
22
23
|
import "../chunk-V7LLYIRO.js";
|
|
23
|
-
import "../chunk-
|
|
24
|
+
import "../chunk-QNC5QRIO.js";
|
|
24
25
|
import "../chunk-5TD7NQIW.js";
|
|
25
26
|
|
|
26
27
|
// src/node/index.ts
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
interface RemoteGitSource {
|
|
2
|
+
repo: string;
|
|
3
|
+
ref?: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
sparse?: boolean;
|
|
6
|
+
}
|
|
1
7
|
interface SectionConfig {
|
|
2
8
|
label: string;
|
|
3
9
|
docsDir: string;
|
|
@@ -5,6 +11,7 @@ interface SectionConfig {
|
|
|
5
11
|
draft?: boolean;
|
|
6
12
|
sitemap?: boolean;
|
|
7
13
|
exclude?: string[];
|
|
14
|
+
git?: RemoteGitSource;
|
|
8
15
|
}
|
|
9
16
|
interface ResolvedSection {
|
|
10
17
|
id: string;
|
|
@@ -85,11 +92,18 @@ interface HubProject {
|
|
|
85
92
|
status?: 'active' | 'beta' | 'planned' | 'deprecated';
|
|
86
93
|
icon?: string;
|
|
87
94
|
tags?: string[];
|
|
95
|
+
group?: string;
|
|
96
|
+
hubUrl?: string;
|
|
97
|
+
hubName?: string;
|
|
98
|
+
mode?: 'link' | 'embed';
|
|
99
|
+
git?: RemoteGitSource;
|
|
100
|
+
embedSections?: 'all' | 'public' | string[];
|
|
88
101
|
}
|
|
89
102
|
interface HubConfig {
|
|
90
103
|
projects: HubProject[];
|
|
91
104
|
scan?: string;
|
|
105
|
+
cacheDir?: string;
|
|
92
106
|
}
|
|
93
107
|
declare function defineConfig(config: Partial<ClearifyConfig>): Partial<ClearifyConfig>;
|
|
94
108
|
|
|
95
|
-
export { type ClearifyConfig, type HubConfig, type HubProject, type NavigationItem, type PageFrontmatter, type ResolvedSection, type RouteEntry, type SectionConfig, type SectionNavigation, defineConfig };
|
|
109
|
+
export { type ClearifyConfig, type HubConfig, type HubProject, type NavigationItem, type PageFrontmatter, type RemoteGitSource, type ResolvedSection, type RouteEntry, type SectionConfig, type SectionNavigation, defineConfig };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clearifyPlugin
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-2RAA6477.js";
|
|
4
4
|
import "../chunk-B2Q23JW3.js";
|
|
5
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-PVTUVUBK.js";
|
|
6
|
+
import "../chunk-AJBCHKBP.js";
|
|
6
7
|
import "../chunk-V7LLYIRO.js";
|
|
7
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-QNC5QRIO.js";
|
|
8
9
|
import "../chunk-5TD7NQIW.js";
|
|
9
10
|
export {
|
|
10
11
|
clearifyPlugin
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marlinjai/clearify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "An open-source documentation site generator. Turn markdown into beautiful docs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
"src/client",
|
|
23
23
|
"src/theme"
|
|
24
24
|
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"dev:self": "node bin/clearify.js dev"
|
|
28
|
+
},
|
|
25
29
|
"dependencies": {
|
|
26
30
|
"@mdx-js/react": "^3.1.1",
|
|
27
31
|
"@mdx-js/rollup": "^3.1.1",
|
|
@@ -68,6 +72,7 @@
|
|
|
68
72
|
"tsup": "^8.3.6",
|
|
69
73
|
"typescript": "^5.7.3"
|
|
70
74
|
},
|
|
75
|
+
"packageManager": "pnpm@9.15.0",
|
|
71
76
|
"license": "MIT",
|
|
72
77
|
"author": "MarlinJai",
|
|
73
78
|
"repository": {
|
|
@@ -88,9 +93,5 @@
|
|
|
88
93
|
"react",
|
|
89
94
|
"mermaid",
|
|
90
95
|
"openapi"
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
"build": "tsup",
|
|
94
|
-
"dev:self": "node bin/clearify.js dev"
|
|
95
|
-
}
|
|
96
|
-
}
|
|
96
|
+
]
|
|
97
|
+
}
|
package/src/theme/Layout.tsx
CHANGED
package/src/theme/Sidebar.tsx
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
|
-
import type { NavigationItem, SectionNavigation } from '../types/index.js';
|
|
4
|
+
import type { NavigationItem, SectionNavigation, HubProject } from '../types/index.js';
|
|
5
5
|
|
|
6
6
|
interface SidebarProps {
|
|
7
7
|
sections: SectionNavigation[];
|
|
8
8
|
open: boolean;
|
|
9
9
|
onClose: () => void;
|
|
10
|
+
hubProject?: Omit<HubProject, 'name'>;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
function findActiveSection(pathname: string, sections: SectionNavigation[]): string {
|
|
@@ -103,7 +104,7 @@ function SectionSwitcher({
|
|
|
103
104
|
);
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
export function Sidebar({ sections, open, onClose }: SidebarProps) {
|
|
107
|
+
export function Sidebar({ sections, open, onClose, hubProject }: SidebarProps) {
|
|
107
108
|
const location = useLocation();
|
|
108
109
|
const navigate = useNavigate();
|
|
109
110
|
|
|
@@ -246,6 +247,37 @@ export function Sidebar({ sections, open, onClose }: SidebarProps) {
|
|
|
246
247
|
}}
|
|
247
248
|
className={clsx('clearify-sidebar', open && 'clearify-sidebar-open')}
|
|
248
249
|
>
|
|
250
|
+
{hubProject?.hubUrl && (
|
|
251
|
+
<a
|
|
252
|
+
href={hubProject.hubUrl}
|
|
253
|
+
style={{
|
|
254
|
+
display: 'flex',
|
|
255
|
+
alignItems: 'center',
|
|
256
|
+
gap: '0.375rem',
|
|
257
|
+
padding: '0.375rem 0.75rem',
|
|
258
|
+
margin: '0 0.5rem 0.5rem',
|
|
259
|
+
fontSize: '0.75rem',
|
|
260
|
+
fontWeight: 500,
|
|
261
|
+
color: 'var(--clearify-text-tertiary)',
|
|
262
|
+
textDecoration: 'none',
|
|
263
|
+
borderRadius: 'var(--clearify-radius-sm)',
|
|
264
|
+
transition: 'color 0.15s, background-color 0.15s',
|
|
265
|
+
}}
|
|
266
|
+
onMouseEnter={(e) => {
|
|
267
|
+
e.currentTarget.style.color = 'var(--clearify-primary)';
|
|
268
|
+
e.currentTarget.style.backgroundColor = 'var(--clearify-primary-soft)';
|
|
269
|
+
}}
|
|
270
|
+
onMouseLeave={(e) => {
|
|
271
|
+
e.currentTarget.style.color = 'var(--clearify-text-tertiary)';
|
|
272
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
273
|
+
}}
|
|
274
|
+
>
|
|
275
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
276
|
+
<polyline points="15 18 9 12 15 6" />
|
|
277
|
+
</svg>
|
|
278
|
+
{hubProject.hubName ?? 'Hub'}
|
|
279
|
+
</a>
|
|
280
|
+
)}
|
|
249
281
|
{showSwitcher && (
|
|
250
282
|
<SectionSwitcher
|
|
251
283
|
sections={sections}
|
|
@@ -1,52 +1,142 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import hub from 'virtual:clearify/hub';
|
|
3
3
|
import { ProjectCard } from './ProjectCard.js';
|
|
4
|
+
import type { HubProject } from '../../types/index.js';
|
|
4
5
|
|
|
5
6
|
interface ProjectGridProps {
|
|
6
7
|
cols?: number;
|
|
7
8
|
children?: React.ReactNode;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
const hasChildren = React.Children.count(children) > 0;
|
|
12
|
-
|
|
11
|
+
function ProjectGridInner({ cols, projects }: { cols: number; projects: HubProject[] }) {
|
|
13
12
|
return (
|
|
14
13
|
<div
|
|
15
14
|
style={{
|
|
16
15
|
display: 'grid',
|
|
17
16
|
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
18
17
|
gap: '0.875rem',
|
|
19
|
-
marginBottom: '1.5rem',
|
|
20
18
|
}}
|
|
21
19
|
className="clearify-project-grid"
|
|
22
20
|
>
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/>
|
|
36
|
-
))}
|
|
37
|
-
|
|
38
|
-
<style>{`
|
|
39
|
-
@media (max-width: 900px) {
|
|
40
|
-
.clearify-project-grid {
|
|
41
|
-
grid-template-columns: repeat(2, 1fr) !important;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
@media (max-width: 640px) {
|
|
45
|
-
.clearify-project-grid {
|
|
46
|
-
grid-template-columns: 1fr !important;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
`}</style>
|
|
21
|
+
{projects.map((project) => (
|
|
22
|
+
<ProjectCard
|
|
23
|
+
key={project.name}
|
|
24
|
+
name={project.name}
|
|
25
|
+
description={project.description}
|
|
26
|
+
href={project.href}
|
|
27
|
+
repo={project.repo}
|
|
28
|
+
status={project.status}
|
|
29
|
+
icon={project.icon}
|
|
30
|
+
tags={project.tags}
|
|
31
|
+
/>
|
|
32
|
+
))}
|
|
50
33
|
</div>
|
|
51
34
|
);
|
|
52
35
|
}
|
|
36
|
+
|
|
37
|
+
export function ProjectGrid({ cols = 3, children }: ProjectGridProps) {
|
|
38
|
+
const hasChildren = React.Children.count(children) > 0;
|
|
39
|
+
|
|
40
|
+
if (hasChildren) {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
style={{
|
|
44
|
+
display: 'grid',
|
|
45
|
+
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
46
|
+
gap: '0.875rem',
|
|
47
|
+
marginBottom: '1.5rem',
|
|
48
|
+
}}
|
|
49
|
+
className="clearify-project-grid"
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
<style>{gridResponsiveStyles}</style>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const projects: HubProject[] = hub?.projects ?? [];
|
|
58
|
+
const hasGroups = projects.some((p) => p.group);
|
|
59
|
+
|
|
60
|
+
if (!hasGroups) {
|
|
61
|
+
return (
|
|
62
|
+
<div style={{ marginBottom: '1.5rem' }}>
|
|
63
|
+
<ProjectGridInner cols={cols} projects={projects} />
|
|
64
|
+
<style>{gridResponsiveStyles}</style>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Group projects by their group field, preserving insertion order
|
|
70
|
+
const groupOrder: string[] = [];
|
|
71
|
+
const grouped: Record<string, HubProject[]> = {};
|
|
72
|
+
const ungrouped: HubProject[] = [];
|
|
73
|
+
|
|
74
|
+
for (const project of projects) {
|
|
75
|
+
if (project.group) {
|
|
76
|
+
if (!grouped[project.group]) {
|
|
77
|
+
grouped[project.group] = [];
|
|
78
|
+
groupOrder.push(project.group);
|
|
79
|
+
}
|
|
80
|
+
grouped[project.group].push(project);
|
|
81
|
+
} else {
|
|
82
|
+
ungrouped.push(project);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div style={{ marginBottom: '1.5rem' }}>
|
|
88
|
+
{groupOrder.map((groupName) => (
|
|
89
|
+
<div key={groupName} style={{ marginBottom: '1.5rem' }}>
|
|
90
|
+
<h2
|
|
91
|
+
style={{
|
|
92
|
+
fontSize: '1.25rem',
|
|
93
|
+
fontWeight: 600,
|
|
94
|
+
marginBottom: '0.75rem',
|
|
95
|
+
marginTop: 0,
|
|
96
|
+
color: 'var(--clearify-text)',
|
|
97
|
+
letterSpacing: '-0.01em',
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
{groupName}
|
|
101
|
+
</h2>
|
|
102
|
+
<ProjectGridInner cols={cols} projects={grouped[groupName]} />
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
|
|
106
|
+
{ungrouped.length > 0 && (
|
|
107
|
+
<div style={{ marginBottom: '1.5rem' }}>
|
|
108
|
+
{groupOrder.length > 0 && (
|
|
109
|
+
<h2
|
|
110
|
+
style={{
|
|
111
|
+
fontSize: '1.25rem',
|
|
112
|
+
fontWeight: 600,
|
|
113
|
+
marginBottom: '0.75rem',
|
|
114
|
+
marginTop: 0,
|
|
115
|
+
color: 'var(--clearify-text)',
|
|
116
|
+
letterSpacing: '-0.01em',
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
Other Projects
|
|
120
|
+
</h2>
|
|
121
|
+
)}
|
|
122
|
+
<ProjectGridInner cols={cols} projects={ungrouped} />
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
<style>{gridResponsiveStyles}</style>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const gridResponsiveStyles = `
|
|
132
|
+
@media (max-width: 900px) {
|
|
133
|
+
.clearify-project-grid {
|
|
134
|
+
grid-template-columns: repeat(2, 1fr) !important;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
@media (max-width: 640px) {
|
|
138
|
+
.clearify-project-grid {
|
|
139
|
+
grid-template-columns: 1fr !important;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
`;
|