@marlinjai/clearify 1.11.1 → 1.12.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 +0 -9
- package/dist/config-schema.json +225 -0
- package/dist/node/{chunk-PVTUVUBK.js → chunk-2WLFSQS7.js} +160 -10
- package/dist/node/{chunk-NGWAMBXS.js → chunk-6XHVO5AO.js} +1 -1
- package/dist/node/{chunk-JU53JE4P.js → chunk-6YAEHWC7.js} +8 -2
- package/dist/node/{chunk-PFX7LHRZ.js → chunk-XYS5SV4M.js} +128 -4
- package/dist/node/core/config.d.ts +78 -2
- package/dist/node/core/config.js +11 -3
- package/dist/node/node/build.js +3 -3
- package/dist/node/node/check.js +2 -2
- package/dist/node/node/index.js +4 -4
- package/dist/node/types/index.d.ts +23 -2
- package/dist/node/vite-plugin/index.js +2 -2
- package/package.json +4 -2
- package/src/client/App.tsx +59 -16
- package/src/theme/Header.tsx +32 -0
- package/src/theme/admin/AdminDashboard.tsx +129 -0
- package/src/theme/admin/AdminLayout.tsx +240 -0
- package/src/theme/admin/ProjectsManager.tsx +546 -0
- package/src/theme/admin/SectionsManager.tsx +782 -0
- package/src/theme/admin/SiteSettings.tsx +414 -0
- package/src/theme/admin/components/ConfigPreview.tsx +166 -0
- package/src/theme/admin/components/ConfirmDialog.tsx +73 -0
- package/src/theme/admin/components/FormField.tsx +39 -0
- package/src/theme/admin/components/Modal.tsx +93 -0
- package/src/theme/admin/components/Toast.tsx +59 -0
- package/src/theme/admin/components/index.ts +4 -0
|
@@ -1,10 +1,86 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ClearifyConfig, ClearifyDataConfig, ResolvedSection } from '../types/index.js';
|
|
2
3
|
export { defineConfig } from '../types/index.js';
|
|
3
4
|
|
|
5
|
+
/** Zod schema for clearify.data.json — Tier 1+2 visual-config fields, all optional. */
|
|
6
|
+
declare const ClearifyDataSchema: z.ZodObject<{
|
|
7
|
+
name: z.ZodOptional<z.ZodString>;
|
|
8
|
+
siteUrl: z.ZodOptional<z.ZodString>;
|
|
9
|
+
theme: z.ZodOptional<z.ZodObject<{
|
|
10
|
+
primaryColor: z.ZodOptional<z.ZodString>;
|
|
11
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
12
|
+
light: "light";
|
|
13
|
+
dark: "dark";
|
|
14
|
+
auto: "auto";
|
|
15
|
+
}>>;
|
|
16
|
+
}, z.core.$strip>>;
|
|
17
|
+
logo: z.ZodOptional<z.ZodObject<{
|
|
18
|
+
light: z.ZodOptional<z.ZodString>;
|
|
19
|
+
dark: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, z.core.$strip>>;
|
|
21
|
+
links: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
22
|
+
sections: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
23
|
+
label: z.ZodString;
|
|
24
|
+
docsDir: z.ZodString;
|
|
25
|
+
basePath: z.ZodOptional<z.ZodString>;
|
|
26
|
+
draft: z.ZodOptional<z.ZodBoolean>;
|
|
27
|
+
sitemap: z.ZodOptional<z.ZodBoolean>;
|
|
28
|
+
exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
29
|
+
git: z.ZodOptional<z.ZodObject<{
|
|
30
|
+
repo: z.ZodString;
|
|
31
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
32
|
+
path: z.ZodOptional<z.ZodString>;
|
|
33
|
+
sparse: z.ZodOptional<z.ZodBoolean>;
|
|
34
|
+
}, z.core.$strip>>;
|
|
35
|
+
}, z.core.$strip>>>;
|
|
36
|
+
hub: z.ZodOptional<z.ZodObject<{
|
|
37
|
+
projects: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
38
|
+
name: z.ZodString;
|
|
39
|
+
description: z.ZodString;
|
|
40
|
+
href: z.ZodOptional<z.ZodString>;
|
|
41
|
+
repo: z.ZodOptional<z.ZodString>;
|
|
42
|
+
status: z.ZodDefault<z.ZodEnum<{
|
|
43
|
+
active: "active";
|
|
44
|
+
beta: "beta";
|
|
45
|
+
planned: "planned";
|
|
46
|
+
deprecated: "deprecated";
|
|
47
|
+
}>>;
|
|
48
|
+
icon: z.ZodOptional<z.ZodString>;
|
|
49
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
50
|
+
group: z.ZodOptional<z.ZodString>;
|
|
51
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
52
|
+
link: "link";
|
|
53
|
+
embed: "embed";
|
|
54
|
+
inject: "inject";
|
|
55
|
+
}>>;
|
|
56
|
+
git: z.ZodOptional<z.ZodObject<{
|
|
57
|
+
repo: z.ZodString;
|
|
58
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
59
|
+
path: z.ZodOptional<z.ZodString>;
|
|
60
|
+
sparse: z.ZodOptional<z.ZodBoolean>;
|
|
61
|
+
}, z.core.$strip>>;
|
|
62
|
+
embedSections: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodLiteral<"public">, z.ZodArray<z.ZodString>]>>;
|
|
63
|
+
injectInto: z.ZodOptional<z.ZodString>;
|
|
64
|
+
docsPath: z.ZodOptional<z.ZodString>;
|
|
65
|
+
}, z.core.$strip>>>;
|
|
66
|
+
scan: z.ZodOptional<z.ZodString>;
|
|
67
|
+
cacheDir: z.ZodOptional<z.ZodString>;
|
|
68
|
+
}, z.core.$strip>>;
|
|
69
|
+
}, z.core.$strip>;
|
|
70
|
+
/**
|
|
71
|
+
* Deep-merge utility where JSON (overlay) values win over base values.
|
|
72
|
+
* Plain objects merge recursively. Arrays are replaced entirely.
|
|
73
|
+
* Undefined values in the overlay are skipped.
|
|
74
|
+
*/
|
|
75
|
+
declare function deepMergeJsonWins<T extends Record<string, any>>(base: T, json: Partial<T>): T;
|
|
76
|
+
/** Load and validate clearify.data.json from a project root. Returns `{}` on missing/invalid file. */
|
|
77
|
+
declare function loadDataConfig(root: string): ClearifyDataConfig;
|
|
78
|
+
/** Validate and atomically write clearify.data.json. */
|
|
79
|
+
declare function writeDataConfig(root: string, data: unknown): void;
|
|
4
80
|
declare const defaultConfig: ClearifyConfig;
|
|
5
81
|
declare function loadUserConfig(root: string): Promise<Partial<ClearifyConfig>>;
|
|
6
82
|
declare function resolveConfig(userConfig?: Partial<ClearifyConfig>, root?: string): ClearifyConfig;
|
|
7
83
|
declare function resolveSections(config: ClearifyConfig, root: string): Promise<ResolvedSection[]>;
|
|
8
84
|
declare function scanHubProjects(config: ClearifyConfig, root: string): Promise<ClearifyConfig>;
|
|
9
85
|
|
|
10
|
-
export { defaultConfig, loadUserConfig, resolveConfig, resolveSections, scanHubProjects };
|
|
86
|
+
export { ClearifyDataSchema, deepMergeJsonWins, defaultConfig, loadDataConfig, loadUserConfig, resolveConfig, resolveSections, scanHubProjects, writeDataConfig };
|
package/dist/node/core/config.js
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ClearifyDataSchema,
|
|
3
|
+
deepMergeJsonWins,
|
|
2
4
|
defaultConfig,
|
|
5
|
+
loadDataConfig,
|
|
3
6
|
loadUserConfig,
|
|
4
7
|
resolveConfig,
|
|
5
8
|
resolveSections,
|
|
6
|
-
scanHubProjects
|
|
7
|
-
|
|
9
|
+
scanHubProjects,
|
|
10
|
+
writeDataConfig
|
|
11
|
+
} from "../chunk-2WLFSQS7.js";
|
|
8
12
|
import "../chunk-AJBCHKBP.js";
|
|
9
13
|
import {
|
|
10
14
|
defineConfig
|
|
11
15
|
} from "../chunk-V7LLYIRO.js";
|
|
12
16
|
export {
|
|
17
|
+
ClearifyDataSchema,
|
|
18
|
+
deepMergeJsonWins,
|
|
13
19
|
defaultConfig,
|
|
14
20
|
defineConfig,
|
|
21
|
+
loadDataConfig,
|
|
15
22
|
loadUserConfig,
|
|
16
23
|
resolveConfig,
|
|
17
24
|
resolveSections,
|
|
18
|
-
scanHubProjects
|
|
25
|
+
scanHubProjects,
|
|
26
|
+
writeDataConfig
|
|
19
27
|
};
|
package/dist/node/node/build.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildSite
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-6YAEHWC7.js";
|
|
4
4
|
import "../chunk-PRTER35L.js";
|
|
5
5
|
import "../chunk-SCZZB7OE.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-XYS5SV4M.js";
|
|
7
7
|
import "../chunk-B2Q23JW3.js";
|
|
8
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-2WLFSQS7.js";
|
|
9
9
|
import "../chunk-AJBCHKBP.js";
|
|
10
10
|
import "../chunk-V7LLYIRO.js";
|
|
11
11
|
import "../chunk-QTGT76RY.js";
|
package/dist/node/node/check.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
checkFrontmatter,
|
|
3
3
|
checkLinks
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-6XHVO5AO.js";
|
|
5
|
+
import "../chunk-2WLFSQS7.js";
|
|
6
6
|
import "../chunk-AJBCHKBP.js";
|
|
7
7
|
import "../chunk-V7LLYIRO.js";
|
|
8
8
|
import "../chunk-QTGT76RY.js";
|
package/dist/node/node/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildSite
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-6YAEHWC7.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-XYS5SV4M.js";
|
|
11
11
|
import "../chunk-B2Q23JW3.js";
|
|
12
12
|
import {
|
|
13
13
|
init
|
|
@@ -15,11 +15,11 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
checkFrontmatter,
|
|
17
17
|
checkLinks
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-6XHVO5AO.js";
|
|
19
19
|
import {
|
|
20
20
|
loadUserConfig,
|
|
21
21
|
resolveConfig
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-2WLFSQS7.js";
|
|
23
23
|
import "../chunk-AJBCHKBP.js";
|
|
24
24
|
import "../chunk-V7LLYIRO.js";
|
|
25
25
|
import "../chunk-QTGT76RY.js";
|
|
@@ -102,15 +102,36 @@ interface HubProject {
|
|
|
102
102
|
group?: string;
|
|
103
103
|
hubUrl?: string;
|
|
104
104
|
hubName?: string;
|
|
105
|
-
mode?: 'link' | 'embed';
|
|
105
|
+
mode?: 'link' | 'embed' | 'inject';
|
|
106
106
|
git?: RemoteGitSource;
|
|
107
107
|
embedSections?: 'all' | 'public' | string[];
|
|
108
|
+
injectInto?: string;
|
|
109
|
+
docsPath?: string;
|
|
108
110
|
}
|
|
109
111
|
interface HubConfig {
|
|
110
112
|
projects: HubProject[];
|
|
111
113
|
scan?: string;
|
|
112
114
|
cacheDir?: string;
|
|
113
115
|
}
|
|
116
|
+
/** Tier 1+2 fields that can live in clearify.data.json for visual editing. */
|
|
117
|
+
interface ClearifyDataConfig {
|
|
118
|
+
name?: string;
|
|
119
|
+
siteUrl?: string;
|
|
120
|
+
theme?: {
|
|
121
|
+
primaryColor?: string;
|
|
122
|
+
mode?: 'light' | 'dark' | 'auto';
|
|
123
|
+
};
|
|
124
|
+
logo?: {
|
|
125
|
+
light?: string;
|
|
126
|
+
dark?: string;
|
|
127
|
+
};
|
|
128
|
+
links?: {
|
|
129
|
+
github?: string;
|
|
130
|
+
[key: string]: string | undefined;
|
|
131
|
+
};
|
|
132
|
+
sections?: SectionConfig[];
|
|
133
|
+
hub?: HubConfig;
|
|
134
|
+
}
|
|
114
135
|
declare function defineConfig(config: Partial<ClearifyConfig>): Partial<ClearifyConfig>;
|
|
115
136
|
|
|
116
|
-
export { type ClearifyConfig, type DocCategory, type HubConfig, type HubProject, type NavigationItem, type PageFrontmatter, type RemoteGitSource, type ResolvedSection, type RouteEntry, type SectionConfig, type SectionNavigation, defineConfig };
|
|
137
|
+
export { type ClearifyConfig, type ClearifyDataConfig, type DocCategory, type HubConfig, type HubProject, type NavigationItem, type PageFrontmatter, type RemoteGitSource, type ResolvedSection, type RouteEntry, type SectionConfig, type SectionNavigation, defineConfig };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clearifyPlugin
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-XYS5SV4M.js";
|
|
4
4
|
import "../chunk-B2Q23JW3.js";
|
|
5
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-2WLFSQS7.js";
|
|
6
6
|
import "../chunk-AJBCHKBP.js";
|
|
7
7
|
import "../chunk-V7LLYIRO.js";
|
|
8
8
|
import "../chunk-QTGT76RY.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marlinjai/clearify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "An open-source documentation site generator. Turn markdown into beautiful docs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"./config": {
|
|
15
15
|
"types": "./dist/node/core/config.d.ts",
|
|
16
16
|
"import": "./dist/node/core/config.js"
|
|
17
|
-
}
|
|
17
|
+
},
|
|
18
|
+
"./config-schema.json": "./dist/config-schema.json"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"bin",
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
],
|
|
25
26
|
"scripts": {
|
|
26
27
|
"build": "tsup",
|
|
28
|
+
"postbuild": "node scripts/generate-schema.mjs",
|
|
27
29
|
"dev:self": "node bin/clearify.js dev",
|
|
28
30
|
"prepare": "husky",
|
|
29
31
|
"release": "semantic-release"
|
package/src/client/App.tsx
CHANGED
|
@@ -7,12 +7,20 @@ import routes from 'virtual:clearify/routes';
|
|
|
7
7
|
import config from 'virtual:clearify/config';
|
|
8
8
|
// @ts-expect-error virtual module
|
|
9
9
|
import sections from 'virtual:clearify/navigation';
|
|
10
|
+
// @ts-expect-error virtual module
|
|
11
|
+
import adminEnabled from 'virtual:clearify/admin-enabled';
|
|
10
12
|
import { Layout } from '../theme/Layout.js';
|
|
11
13
|
import { Head } from '../theme/Head.js';
|
|
12
14
|
import { NotFound } from '../theme/NotFound.js';
|
|
13
15
|
import { mdxComponents } from '../theme/MDXComponents.js';
|
|
14
16
|
import '../theme/styles/globals.css';
|
|
15
17
|
|
|
18
|
+
const AdminLayout = React.lazy(() => import('../theme/admin/AdminLayout.js'));
|
|
19
|
+
const AdminDashboard = React.lazy(() => import('../theme/admin/AdminDashboard.js'));
|
|
20
|
+
const ProjectsManager = React.lazy(() => import('../theme/admin/ProjectsManager.js'));
|
|
21
|
+
const SectionsManager = React.lazy(() => import('../theme/admin/SectionsManager.js'));
|
|
22
|
+
const SiteSettings = React.lazy(() => import('../theme/admin/SiteSettings.js'));
|
|
23
|
+
|
|
16
24
|
interface RouteEntry {
|
|
17
25
|
path: string;
|
|
18
26
|
component?: () => Promise<any>;
|
|
@@ -74,24 +82,59 @@ function PageWrapper({ loader, fallbackFrontmatter }: { loader: () => Promise<an
|
|
|
74
82
|
);
|
|
75
83
|
}
|
|
76
84
|
|
|
85
|
+
function AdminDisabled() {
|
|
86
|
+
return (
|
|
87
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', color: 'var(--clearify-text-secondary)', fontFamily: 'var(--font-sans)' }}>
|
|
88
|
+
Admin panel is only available in dev mode.
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
function AppRoutes() {
|
|
78
94
|
return (
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
<Routes>
|
|
96
|
+
{/* Admin routes — outside Layout, lazy-loaded */}
|
|
97
|
+
{adminEnabled ? (
|
|
98
|
+
<Route
|
|
99
|
+
path="/admin"
|
|
100
|
+
element={
|
|
101
|
+
<React.Suspense fallback={<div style={{ padding: '2rem', color: 'var(--clearify-text-secondary)' }}>Loading...</div>}>
|
|
102
|
+
<AdminLayout />
|
|
103
|
+
</React.Suspense>
|
|
104
|
+
}
|
|
105
|
+
>
|
|
106
|
+
<Route index element={<AdminDashboard />} />
|
|
107
|
+
<Route path="projects" element={<ProjectsManager />} />
|
|
108
|
+
<Route path="sections" element={<SectionsManager />} />
|
|
109
|
+
<Route path="settings" element={<SiteSettings />} />
|
|
110
|
+
</Route>
|
|
111
|
+
) : (
|
|
112
|
+
<Route path="/admin/*" element={<AdminDisabled />} />
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
{/* Docs routes — wrapped in Layout */}
|
|
116
|
+
<Route
|
|
117
|
+
path="*"
|
|
118
|
+
element={
|
|
119
|
+
<Layout config={config} sections={sections}>
|
|
120
|
+
<Routes>
|
|
121
|
+
{(routes as RouteEntry[]).map((route) =>
|
|
122
|
+
route.redirectTo ? (
|
|
123
|
+
<Route key={route.path} path={route.path} element={<Navigate to={route.redirectTo} replace />} />
|
|
124
|
+
) : (
|
|
125
|
+
<Route
|
|
126
|
+
key={route.path}
|
|
127
|
+
path={route.path}
|
|
128
|
+
element={<PageWrapper loader={route.component!} fallbackFrontmatter={route.frontmatter} />}
|
|
129
|
+
/>
|
|
130
|
+
)
|
|
131
|
+
)}
|
|
132
|
+
<Route path="*" element={<NotFound />} />
|
|
133
|
+
</Routes>
|
|
134
|
+
</Layout>
|
|
135
|
+
}
|
|
136
|
+
/>
|
|
137
|
+
</Routes>
|
|
95
138
|
);
|
|
96
139
|
}
|
|
97
140
|
|
package/src/theme/Header.tsx
CHANGED
|
@@ -2,6 +2,8 @@ import React from 'react';
|
|
|
2
2
|
import { Link } from 'react-router-dom';
|
|
3
3
|
import { useTheme } from './ThemeProvider.js';
|
|
4
4
|
import { Search } from './Search.js';
|
|
5
|
+
// @ts-expect-error virtual module
|
|
6
|
+
import adminEnabled from 'virtual:clearify/admin-enabled';
|
|
5
7
|
|
|
6
8
|
interface HeaderProps {
|
|
7
9
|
name: string;
|
|
@@ -158,6 +160,36 @@ export function Header({ name, links, logo, onMenuToggle }: HeaderProps) {
|
|
|
158
160
|
</a>
|
|
159
161
|
)}
|
|
160
162
|
|
|
163
|
+
{adminEnabled && (
|
|
164
|
+
<Link
|
|
165
|
+
to="/admin"
|
|
166
|
+
aria-label="Admin panel"
|
|
167
|
+
style={{
|
|
168
|
+
display: 'inline-flex',
|
|
169
|
+
alignItems: 'center',
|
|
170
|
+
justifyContent: 'center',
|
|
171
|
+
width: 36,
|
|
172
|
+
height: 36,
|
|
173
|
+
borderRadius: 'var(--clearify-radius-sm)',
|
|
174
|
+
color: 'var(--clearify-text-secondary)',
|
|
175
|
+
transition: 'color 0.15s, background-color 0.15s',
|
|
176
|
+
}}
|
|
177
|
+
onMouseEnter={(e) => {
|
|
178
|
+
e.currentTarget.style.color = 'var(--clearify-text)';
|
|
179
|
+
e.currentTarget.style.backgroundColor = 'var(--clearify-bg-secondary)';
|
|
180
|
+
}}
|
|
181
|
+
onMouseLeave={(e) => {
|
|
182
|
+
e.currentTarget.style.color = 'var(--clearify-text-secondary)';
|
|
183
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
187
|
+
<circle cx="12" cy="12" r="3" />
|
|
188
|
+
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
|
189
|
+
</svg>
|
|
190
|
+
</Link>
|
|
191
|
+
)}
|
|
192
|
+
|
|
161
193
|
<button
|
|
162
194
|
onClick={toggleTheme}
|
|
163
195
|
style={{
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
const cardStyle: React.CSSProperties = {
|
|
5
|
+
backgroundColor: 'var(--clearify-surface-elevated)',
|
|
6
|
+
border: '1px solid var(--clearify-border)',
|
|
7
|
+
borderRadius: 'var(--clearify-radius)',
|
|
8
|
+
padding: '1.5rem',
|
|
9
|
+
boxShadow: 'var(--clearify-shadow)',
|
|
10
|
+
textDecoration: 'none',
|
|
11
|
+
color: 'inherit',
|
|
12
|
+
transition: 'box-shadow 0.15s, border-color 0.15s',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
interface DashboardCardProps {
|
|
16
|
+
title: string;
|
|
17
|
+
description: string;
|
|
18
|
+
to: string;
|
|
19
|
+
icon: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function DashboardCard({ title, description, to, icon }: DashboardCardProps) {
|
|
23
|
+
return (
|
|
24
|
+
<Link
|
|
25
|
+
to={to}
|
|
26
|
+
style={cardStyle}
|
|
27
|
+
onMouseEnter={(e) => {
|
|
28
|
+
e.currentTarget.style.boxShadow = 'var(--clearify-shadow-glow)';
|
|
29
|
+
e.currentTarget.style.borderColor = 'var(--clearify-primary)';
|
|
30
|
+
}}
|
|
31
|
+
onMouseLeave={(e) => {
|
|
32
|
+
e.currentTarget.style.boxShadow = 'var(--clearify-shadow)';
|
|
33
|
+
e.currentTarget.style.borderColor = 'var(--clearify-border)';
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '0.75rem' }}>
|
|
37
|
+
<div
|
|
38
|
+
style={{
|
|
39
|
+
width: 40,
|
|
40
|
+
height: 40,
|
|
41
|
+
borderRadius: 'var(--clearify-radius-sm)',
|
|
42
|
+
background: 'var(--clearify-primary-soft)',
|
|
43
|
+
display: 'flex',
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
justifyContent: 'center',
|
|
46
|
+
color: 'var(--clearify-primary)',
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
{icon}
|
|
50
|
+
</div>
|
|
51
|
+
<h3 style={{ fontSize: '1.125rem', fontWeight: 650, color: 'var(--clearify-text)' }}>
|
|
52
|
+
{title}
|
|
53
|
+
</h3>
|
|
54
|
+
</div>
|
|
55
|
+
<p style={{ fontSize: '0.875rem', color: 'var(--clearify-text-secondary)', lineHeight: 1.5 }}>
|
|
56
|
+
{description}
|
|
57
|
+
</p>
|
|
58
|
+
</Link>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function AdminDashboard() {
|
|
63
|
+
return (
|
|
64
|
+
<div style={{ padding: '2rem', maxWidth: '64rem', margin: '0 auto' }}>
|
|
65
|
+
<h1
|
|
66
|
+
style={{
|
|
67
|
+
fontSize: '2rem',
|
|
68
|
+
fontWeight: 800,
|
|
69
|
+
letterSpacing: '-0.035em',
|
|
70
|
+
color: 'var(--clearify-text)',
|
|
71
|
+
marginBottom: '0.5rem',
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
Admin Dashboard
|
|
75
|
+
</h1>
|
|
76
|
+
<p style={{ color: 'var(--clearify-text-secondary)', marginBottom: '2rem' }}>
|
|
77
|
+
Manage your Clearify documentation site.
|
|
78
|
+
</p>
|
|
79
|
+
|
|
80
|
+
<div
|
|
81
|
+
style={{
|
|
82
|
+
display: 'grid',
|
|
83
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
|
|
84
|
+
gap: '1.25rem',
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
<DashboardCard
|
|
88
|
+
title="Projects"
|
|
89
|
+
description="Manage hub projects, add or remove project entries."
|
|
90
|
+
to="/admin/projects"
|
|
91
|
+
icon={
|
|
92
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
93
|
+
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />
|
|
94
|
+
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
|
|
95
|
+
</svg>
|
|
96
|
+
}
|
|
97
|
+
/>
|
|
98
|
+
<DashboardCard
|
|
99
|
+
title="Sections"
|
|
100
|
+
description="Organize documentation sections and navigation structure."
|
|
101
|
+
to="/admin/sections"
|
|
102
|
+
icon={
|
|
103
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
104
|
+
<line x1="8" y1="6" x2="21" y2="6" />
|
|
105
|
+
<line x1="8" y1="12" x2="21" y2="12" />
|
|
106
|
+
<line x1="8" y1="18" x2="21" y2="18" />
|
|
107
|
+
<line x1="3" y1="6" x2="3.01" y2="6" />
|
|
108
|
+
<line x1="3" y1="12" x2="3.01" y2="12" />
|
|
109
|
+
<line x1="3" y1="18" x2="3.01" y2="18" />
|
|
110
|
+
</svg>
|
|
111
|
+
}
|
|
112
|
+
/>
|
|
113
|
+
<DashboardCard
|
|
114
|
+
title="Site Settings"
|
|
115
|
+
description="Configure site name, colors, links, and other settings."
|
|
116
|
+
to="/admin/settings"
|
|
117
|
+
icon={
|
|
118
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
119
|
+
<circle cx="12" cy="12" r="3" />
|
|
120
|
+
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
|
121
|
+
</svg>
|
|
122
|
+
}
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default AdminDashboard;
|