@marlinjai/clearify 1.8.0 → 1.9.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 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
- npm install @marlinjai/clearify --save-dev
23
+ pnpm add -D @marlinjai/clearify
24
24
  ```
25
25
 
26
26
  Scaffold your project:
27
27
 
28
28
  ```bash
29
- npx clearify init
29
+ pnpm exec clearify init
30
30
  ```
31
31
 
32
32
  Start the dev server:
33
33
 
34
34
  ```bash
35
- npx clearify dev
35
+ pnpm exec clearify dev
36
36
  ```
37
37
 
38
38
  Your docs are live at `http://localhost:4747`.
@@ -2,10 +2,10 @@ import {
2
2
  loadUserConfig,
3
3
  resolveConfig,
4
4
  resolveSections
5
- } from "./chunk-YMRRZEYA.js";
5
+ } from "./chunk-PQMKRFBQ.js";
6
6
  import {
7
7
  scanDocs
8
- } from "./chunk-6PUBRHS7.js";
8
+ } from "./chunk-QNC5QRIO.js";
9
9
 
10
10
  // src/node/check.ts
11
11
  import { resolve } from "path";
@@ -8,15 +8,15 @@ import {
8
8
  } from "./chunk-SCZZB7OE.js";
9
9
  import {
10
10
  clearifyPlugin
11
- } from "./chunk-3WWPYCGQ.js";
11
+ } from "./chunk-IF6GI4WU.js";
12
12
  import {
13
13
  loadUserConfig,
14
14
  resolveConfig,
15
15
  resolveSections
16
- } from "./chunk-YMRRZEYA.js";
16
+ } from "./chunk-PQMKRFBQ.js";
17
17
  import {
18
18
  buildSectionData
19
- } from "./chunk-6PUBRHS7.js";
19
+ } from "./chunk-QNC5QRIO.js";
20
20
 
21
21
  // src/node/build.ts
22
22
  import { build as viteBuild } from "vite";
@@ -6,10 +6,10 @@ import {
6
6
  resolveConfig,
7
7
  resolveSections,
8
8
  scanHubProjects
9
- } from "./chunk-YMRRZEYA.js";
9
+ } from "./chunk-PQMKRFBQ.js";
10
10
  import {
11
11
  buildSectionData
12
- } from "./chunk-6PUBRHS7.js";
12
+ } from "./chunk-QNC5QRIO.js";
13
13
 
14
14
  // src/vite-plugin/index.ts
15
15
  import { resolve } from "path";
@@ -38,7 +38,8 @@ var HubProjectSchema = z.object({
38
38
  repo: z.string().optional(),
39
39
  status: z.enum(["active", "beta", "planned", "deprecated"]).default("active"),
40
40
  icon: z.string().optional(),
41
- tags: z.array(z.string()).optional()
41
+ tags: z.array(z.string()).optional(),
42
+ group: z.string().optional()
42
43
  });
43
44
  var HubProjectPartialSchema = z.object({
44
45
  description: z.string(),
@@ -46,7 +47,10 @@ var HubProjectPartialSchema = z.object({
46
47
  repo: z.string().optional(),
47
48
  status: z.enum(["active", "beta", "planned", "deprecated"]).default("active"),
48
49
  icon: z.string().optional(),
49
- tags: z.array(z.string()).optional()
50
+ tags: z.array(z.string()).optional(),
51
+ group: z.string().optional(),
52
+ hubUrl: z.string().optional(),
53
+ hubName: z.string().optional()
50
54
  }).optional();
51
55
  var HubConfigSchema = z.object({
52
56
  projects: z.array(HubProjectSchema).default([]),
@@ -236,7 +240,8 @@ async function scanHubProjects(config, root) {
236
240
  repo: hp.repo,
237
241
  status: hp.status,
238
242
  icon: hp.icon,
239
- tags: hp.tags
243
+ tags: hp.tags,
244
+ group: hp.group
240
245
  });
241
246
  } catch {
242
247
  }
@@ -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 groups = /* @__PURE__ */ new Map();
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
- const nav = [];
66
- const rootFiles = groups.get(rootDir) ?? [];
67
- for (const doc of rootFiles) {
68
- const item = { label: doc.frontmatter.title, path: doc.routePath };
69
- if (doc.frontmatter.icon) item.icon = doc.frontmatter.icon;
70
- nav.push(item);
71
- }
72
- const dirs = [...groups.keys()].filter((d) => d !== rootDir).sort();
73
- for (const dir of dirs) {
74
- const files = groups.get(dir);
75
- const label = toTitleCase(basename(dir));
76
- const children = files.map((doc) => {
77
- const child = { label: doc.frontmatter.title, path: doc.routePath };
78
- if (doc.frontmatter.icon) child.icon = doc.frontmatter.icon;
79
- return child;
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
- nav.push({ label, children });
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 nav;
120
+ return buildGroupChildren(rootDir);
84
121
  }
85
122
  function buildRoutes(docs, sectionId) {
86
123
  return docs.map((doc) => ({
@@ -4,7 +4,7 @@ import {
4
4
  resolveConfig,
5
5
  resolveSections,
6
6
  scanHubProjects
7
- } from "../chunk-YMRRZEYA.js";
7
+ } from "../chunk-PQMKRFBQ.js";
8
8
  import {
9
9
  defineConfig
10
10
  } from "../chunk-V7LLYIRO.js";
@@ -3,7 +3,7 @@ import {
3
3
  buildRoutes,
4
4
  buildSectionData,
5
5
  scanDocs
6
- } from "../chunk-6PUBRHS7.js";
6
+ } from "../chunk-QNC5QRIO.js";
7
7
  import "../chunk-5TD7NQIW.js";
8
8
  export {
9
9
  buildNavigation,
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  buildSite
3
- } from "../chunk-W3MZOFJQ.js";
3
+ } from "../chunk-GGUDZD4K.js";
4
4
  import "../chunk-PRTER35L.js";
5
5
  import "../chunk-SCZZB7OE.js";
6
- import "../chunk-3WWPYCGQ.js";
6
+ import "../chunk-IF6GI4WU.js";
7
7
  import "../chunk-B2Q23JW3.js";
8
- import "../chunk-YMRRZEYA.js";
8
+ import "../chunk-PQMKRFBQ.js";
9
9
  import "../chunk-V7LLYIRO.js";
10
- import "../chunk-6PUBRHS7.js";
10
+ import "../chunk-QNC5QRIO.js";
11
11
  import "../chunk-5TD7NQIW.js";
12
12
  export {
13
13
  buildSite
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  checkLinks
3
- } from "../chunk-4BOXMNVR.js";
4
- import "../chunk-YMRRZEYA.js";
3
+ } from "../chunk-77VHQV7W.js";
4
+ import "../chunk-PQMKRFBQ.js";
5
5
  import "../chunk-V7LLYIRO.js";
6
- import "../chunk-6PUBRHS7.js";
6
+ import "../chunk-QNC5QRIO.js";
7
7
  import "../chunk-5TD7NQIW.js";
8
8
  export {
9
9
  checkLinks
@@ -1,26 +1,26 @@
1
1
  import {
2
2
  buildSite
3
- } from "../chunk-W3MZOFJQ.js";
3
+ } from "../chunk-GGUDZD4K.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-3WWPYCGQ.js";
10
+ } from "../chunk-IF6GI4WU.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-4BOXMNVR.js";
17
+ } from "../chunk-77VHQV7W.js";
18
18
  import {
19
19
  loadUserConfig,
20
20
  resolveConfig
21
- } from "../chunk-YMRRZEYA.js";
21
+ } from "../chunk-PQMKRFBQ.js";
22
22
  import "../chunk-V7LLYIRO.js";
23
- import "../chunk-6PUBRHS7.js";
23
+ import "../chunk-QNC5QRIO.js";
24
24
  import "../chunk-5TD7NQIW.js";
25
25
 
26
26
  // src/node/index.ts
@@ -85,6 +85,9 @@ interface HubProject {
85
85
  status?: 'active' | 'beta' | 'planned' | 'deprecated';
86
86
  icon?: string;
87
87
  tags?: string[];
88
+ group?: string;
89
+ hubUrl?: string;
90
+ hubName?: string;
88
91
  }
89
92
  interface HubConfig {
90
93
  projects: HubProject[];
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  clearifyPlugin
3
- } from "../chunk-3WWPYCGQ.js";
3
+ } from "../chunk-IF6GI4WU.js";
4
4
  import "../chunk-B2Q23JW3.js";
5
- import "../chunk-YMRRZEYA.js";
5
+ import "../chunk-PQMKRFBQ.js";
6
6
  import "../chunk-V7LLYIRO.js";
7
- import "../chunk-6PUBRHS7.js";
7
+ import "../chunk-QNC5QRIO.js";
8
8
  import "../chunk-5TD7NQIW.js";
9
9
  export {
10
10
  clearifyPlugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marlinjai/clearify",
3
- "version": "1.8.0",
3
+ "version": "1.9.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
- "scripts": {
93
- "build": "tsup",
94
- "dev:self": "node bin/clearify.js dev"
95
- }
96
- }
96
+ ]
97
+ }
@@ -33,6 +33,7 @@ export function Layout({ config, sections, children }: LayoutProps) {
33
33
  sections={sections}
34
34
  open={sidebarOpen}
35
35
  onClose={() => setSidebarOpen(false)}
36
+ hubProject={config.hubProject}
36
37
  />
37
38
  <Content>{children}</Content>
38
39
  <TableOfContents />
@@ -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
- export function ProjectGrid({ cols = 3, children }: ProjectGridProps) {
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
- {hasChildren
24
- ? children
25
- : hub?.projects.map((project) => (
26
- <ProjectCard
27
- key={project.name}
28
- name={project.name}
29
- description={project.description}
30
- href={project.href}
31
- repo={project.repo}
32
- status={project.status}
33
- icon={project.icon}
34
- tags={project.tags}
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
+ `;