@rimelight/cms 0.0.2 → 0.0.4
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/LICENSE +21 -0
- package/dist/bylines-Csggkl-g.d.mts +405 -0
- package/dist/index.d.mts +724 -0
- package/dist/index.mjs +2382 -0
- package/dist/integration.d.mts +33 -0
- package/dist/integration.mjs +161 -0
- package/dist/schema/index.d.mts +2063 -0
- package/dist/schema/index.mjs +176 -0
- package/dist/schema/sqlite.d.mts +2132 -0
- package/dist/schema/sqlite.mjs +137 -0
- package/dist/storage/index.d.mts +59 -0
- package/dist/storage/index.mjs +133 -0
- package/package.json +36 -18
- package/src/admin/api/media-file.ts +39 -0
- package/src/admin/api/media.ts +224 -0
- package/src/admin/layouts/CMSDashboardLayout.astro +191 -0
- package/src/admin/layouts/DefaultParentLayout.astro +50 -0
- package/src/admin/pages/assets.astro +424 -0
- package/src/admin/pages/byline-schema.astro +62 -0
- package/src/admin/pages/bylines.astro +16 -0
- package/src/admin/pages/categories.astro +21 -0
- package/src/admin/pages/content-types.astro +55 -0
- package/src/admin/pages/dashboard.astro +61 -0
- package/src/admin/pages/pages-edit.astro +595 -0
- package/src/admin/pages/pages-index.astro +146 -0
- package/src/admin/pages/pages-new.astro +130 -0
- package/src/admin/pages/pages-preview.astro +111 -0
- package/src/admin/pages/pages-review.astro +236 -0
- package/src/admin/pages/settings.astro +203 -0
- package/src/admin/pages/tags.astro +21 -0
- package/src/admin/pages/templates-edit.astro +234 -0
- package/src/admin/pages/templates-index.astro +95 -0
- package/src/admin/pages/templates-new.astro +193 -0
- package/src/admin/pages/users.astro +74 -0
- package/src/admin/pages/versions.astro +74 -0
- package/src/astro/BlockRenderer.astro +44 -102
- package/src/astro/PageRenderer.astro +12 -5
- package/src/astro/blocks/CalloutBlock.astro +7 -7
- package/src/astro/blocks/CardBlock.astro +5 -5
- package/src/astro/blocks/CodeBlock.astro +2 -2
- package/src/astro/blocks/FileTreeBlock.astro +7 -7
- package/src/astro/blocks/ImageBlock.astro +2 -2
- package/src/astro/blocks/OrderedListBlock.astro +2 -2
- package/src/astro/blocks/ParagraphBlock.astro +3 -3
- package/src/astro/blocks/SectionBlock.astro +6 -6
- package/src/astro/blocks/StepItemBlock.astro +3 -3
- package/src/astro/blocks/StepsBlock.astro +1 -1
- package/src/astro/blocks/TableBlock.astro +6 -6
- package/src/astro/blocks/TabsBlock.astro +9 -9
- package/src/astro/blocks/UnorderedListBlock.astro +2 -2
- package/src/astro/blocks/index.ts +35 -0
- package/src/client/components/RimelightBlock.tsx +19 -19
- package/src/client/components/RimelightBlockPicker.tsx +9 -13
- package/src/client/components/RimelightBylinesManager.tsx +438 -0
- package/src/client/components/RimelightEditor.tsx +3 -3
- package/src/client/components/RimelightPreview.tsx +37 -45
- package/src/client/components/RimelightPropertiesPanel.tsx +368 -26
- package/src/client/components/RimelightTaxonomyManager.tsx +369 -0
- package/src/client/components/RimelightTaxonomyMasterDetail.tsx +412 -0
- package/src/client/components/RimelightTemplateEditor.tsx +6 -8
- package/src/client/components/editors/CalloutEditor.tsx +2 -4
- package/src/client/components/editors/CardsEditor.tsx +2 -4
- package/src/client/components/editors/CodeEditor.tsx +1 -3
- package/src/client/components/editors/FileTreeEditor.tsx +2 -4
- package/src/client/components/editors/ImageEditor.tsx +3 -3
- package/src/client/components/editors/ParagraphEditor.tsx +2 -2
- package/src/client/components/editors/SceneEditor.tsx +6 -12
- package/src/client/components/editors/ScriptEditor.tsx +8 -16
- package/src/client/components/editors/SectionEditor.tsx +4 -4
- package/src/client/components/editors/StepItemEditor.tsx +2 -2
- package/src/client/components/editors/StepsEditor.tsx +2 -4
- package/src/client/components/editors/TabItemEditor.tsx +3 -3
- package/src/client/components/editors/TableEditor.tsx +5 -5
- package/src/client/components/editors/TabsEditor.tsx +2 -4
- package/src/client/components/index.ts +3 -0
- package/src/client/loader.ts +3 -0
- package/src/core/diff.ts +145 -0
- package/src/core/excerpt.ts +159 -0
- package/src/core/preview.ts +87 -0
- package/src/core/registry.ts +0 -86
- package/src/core/types/blocks.ts +6 -1
- package/src/core/types/bylines.ts +14 -0
- package/src/core/types/media.ts +13 -0
- package/src/core/types/taxonomies.ts +20 -0
- package/src/cron/scheduled.ts +57 -0
- package/src/docs/agent.ts +116 -0
- package/src/docs/config.ts +25 -0
- package/src/docs/index.ts +5 -0
- package/src/docs/routing.ts +104 -0
- package/src/docs/sidebar.ts +251 -0
- package/src/docs/toc.ts +47 -0
- package/src/env.d.ts +19 -0
- package/src/index.ts +57 -12
- package/src/integration.ts +250 -0
- package/src/loader/live.ts +254 -0
- package/src/markdown/index.ts +0 -1
- package/src/mcp/index.ts +228 -0
- package/src/schema/bylines.ts +23 -0
- package/src/schema/index.ts +3 -4
- package/src/schema/page_versions.ts +2 -0
- package/src/schema/pages.ts +2 -0
- package/src/schema/site_settings.ts +41 -0
- package/src/schema/sqlite.ts +344 -0
- package/src/schema/taxonomies.ts +49 -0
- package/src/services/bylines.ts +74 -0
- package/src/services/search-indexer.ts +61 -0
- package/src/services/site-settings.ts +108 -0
- package/src/services/taxonomies.ts +276 -0
- package/src/storage/index.ts +256 -0
- package/src/astro/blocks/ApiDocumentationBlock.astro +0 -350
- package/src/astro/blocks/DialogueBlock.astro +0 -23
- package/src/astro/blocks/LivePreviewBlock.astro +0 -27
- package/src/astro/blocks/SceneBlock.astro +0 -65
- package/src/astro/blocks/ScriptBlock.astro +0 -59
- package/src/core/template-sync.ts +0 -75
- package/src/markdown/parser.ts +0 -347
- package/src/migration-tools.ts +0 -334
- package/src/migration.ts +0 -155
- package/src/schema/component_dependencies.ts +0 -21
- package/src/schema/component_metadata.ts +0 -31
- package/src/schema/component_metadata_cache.ts +0 -15
- package/src/schema/component_versions.ts +0 -19
- package/src/search/sync.ts +0 -32
- package/src/services/component-metadata.ts +0 -134
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import type { APIRoute } from "astro"
|
|
2
|
+
import { getR2Bucket, listR2Files, uploadR2File, deleteR2File } from "../../storage/index"
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { storageConfig } from "virtual:rimelight-cms/storage"
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import authAdapter from "virtual:rimelight-cms/auth"
|
|
7
|
+
|
|
8
|
+
export const prerender = false
|
|
9
|
+
|
|
10
|
+
export const GET: APIRoute = async ({ request, locals }) => {
|
|
11
|
+
try {
|
|
12
|
+
const session = await authAdapter.getSession(request)
|
|
13
|
+
if (!session) {
|
|
14
|
+
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), {
|
|
15
|
+
status: 401,
|
|
16
|
+
headers: { "Content-Type": "application/json" }
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
const url = new URL(request.url)
|
|
20
|
+
const prefix = (url.searchParams.get("prefix") ??
|
|
21
|
+
(storageConfig?.prefix as string | undefined) ??
|
|
22
|
+
"media/") as string
|
|
23
|
+
const cursor = (url.searchParams.get("cursor") || undefined) as string | undefined
|
|
24
|
+
const limit = Number(url.searchParams.get("limit")) || 50
|
|
25
|
+
const search = url.searchParams.get("search")?.toLowerCase()
|
|
26
|
+
const typeFilter = url.searchParams.get("type")?.toLowerCase()
|
|
27
|
+
|
|
28
|
+
const bindingName = (storageConfig?.binding as string | undefined) ?? "BLOB"
|
|
29
|
+
const bucket = await getR2Bucket(locals, bindingName)
|
|
30
|
+
|
|
31
|
+
if (!bucket) {
|
|
32
|
+
return new Response(
|
|
33
|
+
JSON.stringify({
|
|
34
|
+
success: false,
|
|
35
|
+
error: `Storage binding "${bindingName}" not found in runtime environment. Ensure R2 bucket is configured in wrangler.jsonc or astro.config.`
|
|
36
|
+
}),
|
|
37
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const apiBasePath = `${url.pathname.replace(/\/$/, "")}/file`
|
|
42
|
+
const result = await listR2Files(bucket, {
|
|
43
|
+
prefix,
|
|
44
|
+
cursor,
|
|
45
|
+
limit,
|
|
46
|
+
publicUrl: storageConfig?.publicUrl as string | undefined,
|
|
47
|
+
apiBasePath
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
let files = result.files
|
|
51
|
+
|
|
52
|
+
if (search) {
|
|
53
|
+
files = files.filter(
|
|
54
|
+
(f) => f.name.toLowerCase().includes(search) || f.key.toLowerCase().includes(search)
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (typeFilter && typeFilter !== "all") {
|
|
59
|
+
files = files.filter((f) => {
|
|
60
|
+
if (typeFilter === "images") return f.mimeType.startsWith("image/")
|
|
61
|
+
if (typeFilter === "videos") return f.mimeType.startsWith("video/")
|
|
62
|
+
if (typeFilter === "audio") return f.mimeType.startsWith("audio/")
|
|
63
|
+
if (typeFilter === "documents")
|
|
64
|
+
return (
|
|
65
|
+
f.mimeType.includes("pdf") ||
|
|
66
|
+
f.mimeType.includes("text") ||
|
|
67
|
+
f.mimeType.includes("document")
|
|
68
|
+
)
|
|
69
|
+
return true
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return new Response(
|
|
74
|
+
JSON.stringify({
|
|
75
|
+
success: true,
|
|
76
|
+
data: {
|
|
77
|
+
files,
|
|
78
|
+
truncated: result.truncated,
|
|
79
|
+
cursor: result.cursor,
|
|
80
|
+
provider: storageConfig?.provider ?? "r2",
|
|
81
|
+
binding: bindingName
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
85
|
+
)
|
|
86
|
+
} catch (error: any) {
|
|
87
|
+
return new Response(
|
|
88
|
+
JSON.stringify({
|
|
89
|
+
success: false,
|
|
90
|
+
error: error.message || "Failed to list media files"
|
|
91
|
+
}),
|
|
92
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
98
|
+
try {
|
|
99
|
+
const session = await authAdapter.getSession(request)
|
|
100
|
+
if (!session) {
|
|
101
|
+
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), {
|
|
102
|
+
status: 401,
|
|
103
|
+
headers: { "Content-Type": "application/json" }
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const bindingName = storageConfig?.binding ?? "BLOB"
|
|
108
|
+
const bucket = await getR2Bucket(locals, bindingName)
|
|
109
|
+
|
|
110
|
+
if (!bucket) {
|
|
111
|
+
return new Response(
|
|
112
|
+
JSON.stringify({
|
|
113
|
+
success: false,
|
|
114
|
+
error: `Storage binding "${bindingName}" not found in runtime environment.`
|
|
115
|
+
}),
|
|
116
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const contentType = request.headers.get("content-type") || ""
|
|
121
|
+
if (!contentType.includes("multipart/form-data")) {
|
|
122
|
+
return new Response(
|
|
123
|
+
JSON.stringify({ success: false, error: "Expected multipart/form-data content type." }),
|
|
124
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const formData = await request.formData()
|
|
129
|
+
const file = formData.get("file")
|
|
130
|
+
|
|
131
|
+
if (!file || !(file instanceof File)) {
|
|
132
|
+
return new Response(
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
success: false,
|
|
135
|
+
error: "No file found in form data payload (field name 'file')."
|
|
136
|
+
}),
|
|
137
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const url = new URL(request.url)
|
|
142
|
+
const apiBasePath = `${url.pathname.replace(/\/$/, "")}/file`
|
|
143
|
+
|
|
144
|
+
const uploaded = await uploadR2File(bucket, file, {
|
|
145
|
+
prefix: (storageConfig?.prefix as string | undefined) ?? "media/",
|
|
146
|
+
publicUrl: storageConfig?.publicUrl as string | undefined,
|
|
147
|
+
apiBasePath
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
return new Response(
|
|
151
|
+
JSON.stringify({
|
|
152
|
+
success: true,
|
|
153
|
+
data: { file: uploaded }
|
|
154
|
+
}),
|
|
155
|
+
{ status: 201, headers: { "Content-Type": "application/json" } }
|
|
156
|
+
)
|
|
157
|
+
} catch (error: any) {
|
|
158
|
+
return new Response(
|
|
159
|
+
JSON.stringify({
|
|
160
|
+
success: false,
|
|
161
|
+
error: error.message || "Failed to upload file to R2"
|
|
162
|
+
}),
|
|
163
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const DELETE: APIRoute = async ({ request, locals }) => {
|
|
169
|
+
try {
|
|
170
|
+
const session = await authAdapter.getSession(request)
|
|
171
|
+
if (!session) {
|
|
172
|
+
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), {
|
|
173
|
+
status: 401,
|
|
174
|
+
headers: { "Content-Type": "application/json" }
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const bindingName = storageConfig?.binding ?? "BLOB"
|
|
179
|
+
const bucket = await getR2Bucket(locals, bindingName)
|
|
180
|
+
|
|
181
|
+
if (!bucket) {
|
|
182
|
+
return new Response(
|
|
183
|
+
JSON.stringify({
|
|
184
|
+
success: false,
|
|
185
|
+
error: `Storage binding "${bindingName}" not found in runtime environment.`
|
|
186
|
+
}),
|
|
187
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let key = ""
|
|
192
|
+
const url = new URL(request.url)
|
|
193
|
+
const queryKey = url.searchParams.get("key")
|
|
194
|
+
|
|
195
|
+
if (queryKey) {
|
|
196
|
+
key = queryKey
|
|
197
|
+
} else {
|
|
198
|
+
const body = await request.json().catch(() => ({}))
|
|
199
|
+
key = body.key
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!key) {
|
|
203
|
+
return new Response(
|
|
204
|
+
JSON.stringify({ success: false, error: "Missing required 'key' parameter for deletion." }),
|
|
205
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
await deleteR2File(bucket, key)
|
|
210
|
+
|
|
211
|
+
return new Response(
|
|
212
|
+
JSON.stringify({ success: true, message: `File "${key}" deleted successfully.` }),
|
|
213
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
214
|
+
)
|
|
215
|
+
} catch (error: any) {
|
|
216
|
+
return new Response(
|
|
217
|
+
JSON.stringify({
|
|
218
|
+
success: false,
|
|
219
|
+
error: error.message || "Failed to delete file from R2"
|
|
220
|
+
}),
|
|
221
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
import ParentLayout from "virtual:rimelight-cms/parent-layout";
|
|
3
|
+
import authAdapter from "virtual:rimelight-cms/auth";
|
|
4
|
+
import RLADashboardGroup from "@rimelight/ui/components/dashboard-group/RLADashboardGroup.astro";
|
|
5
|
+
import RLADashboardSidebar from "@rimelight/ui/components/dashboard-sidebar/RLADashboardSidebar.astro";
|
|
6
|
+
import RLADashboardNavbar from "@rimelight/ui/components/dashboard-navbar/RLADashboardNavbar.astro";
|
|
7
|
+
import RLADashboardPanel from "@rimelight/ui/components/dashboard-panel/RLADashboardPanel.astro";
|
|
8
|
+
import RLADashboardToolbar from "@rimelight/ui/components/dashboard-toolbar/RLADashboardToolbar.astro";
|
|
9
|
+
import RLADashboardSidebarCollapse from "@rimelight/ui/components/dashboard-sidebar-collapse/RLADashboardSidebarCollapse.astro";
|
|
10
|
+
import RLADashboardSidebarToggle from "@rimelight/ui/components/dashboard-sidebar-toggle/RLADashboardSidebarToggle.astro";
|
|
11
|
+
import RLAIcon from "@rimelight/ui/components/icon/RLAIcon.astro";
|
|
12
|
+
import { getRelativeLocaleUrl } from "@rimelight/i18n";
|
|
13
|
+
import "../../client/styles/cms-editor.css";
|
|
14
|
+
|
|
15
|
+
const session = await authAdapter.getSession(Astro.request);
|
|
16
|
+
if (!session) {
|
|
17
|
+
if (authAdapter.handleUnauthorized) {
|
|
18
|
+
const res = await authAdapter.handleUnauthorized(Astro.request);
|
|
19
|
+
if (res instanceof Response) return res;
|
|
20
|
+
}
|
|
21
|
+
return Astro.redirect(getRelativeLocaleUrl(Astro.currentLocale ?? "en", "/auth/sign-in"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const currentLocale = Astro.currentLocale ?? "en";
|
|
25
|
+
|
|
26
|
+
const dashboardItem = {
|
|
27
|
+
icon: "i-lucide-layout-dashboard",
|
|
28
|
+
label: "Dashboard",
|
|
29
|
+
href: getRelativeLocaleUrl(currentLocale, "/cms"),
|
|
30
|
+
id: "dashboard"
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const navigationGroups = [
|
|
34
|
+
{
|
|
35
|
+
title: "Collections",
|
|
36
|
+
items: [
|
|
37
|
+
{ icon: "i-lucide-file-text", label: "Pages", href: getRelativeLocaleUrl(currentLocale, "/cms/pages"), id: "pages" },
|
|
38
|
+
{ icon: "i-lucide-layout-template", label: "Templates", href: getRelativeLocaleUrl(currentLocale, "/cms/templates"), id: "templates" },
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
title: "Manage",
|
|
43
|
+
items: [
|
|
44
|
+
{ icon: "i-lucide-image", label: "Assets", href: getRelativeLocaleUrl(currentLocale, "/cms/assets"), id: "assets" },
|
|
45
|
+
{ icon: "i-lucide-folder", label: "Categories", href: getRelativeLocaleUrl(currentLocale, "/cms/categories"), id: "categories" },
|
|
46
|
+
{ icon: "i-lucide-tag", label: "Tags", href: getRelativeLocaleUrl(currentLocale, "/cms/tags"), id: "tags" },
|
|
47
|
+
{ icon: "i-lucide-pen-tool", label: "Bylines", href: getRelativeLocaleUrl(currentLocale, "/cms/bylines"), id: "bylines" },
|
|
48
|
+
{ icon: "i-lucide-git-branch", label: "Versions", href: getRelativeLocaleUrl(currentLocale, "/cms/versions"), id: "versions" },
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: "Admin",
|
|
53
|
+
items: [
|
|
54
|
+
{ icon: "i-lucide-database", label: "Content Types", href: getRelativeLocaleUrl(currentLocale, "/cms/content-types"), id: "content-types" },
|
|
55
|
+
{ icon: "i-lucide-file-cog", label: "Byline Schema", href: getRelativeLocaleUrl(currentLocale, "/cms/byline-schema"), id: "byline-schema" },
|
|
56
|
+
{ icon: "i-lucide-users", label: "Users", href: getRelativeLocaleUrl(currentLocale, "/cms/users"), id: "users" },
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const settingsNavItem = {
|
|
62
|
+
icon: "i-lucide-settings",
|
|
63
|
+
label: "Settings",
|
|
64
|
+
href: getRelativeLocaleUrl(currentLocale, "/cms/settings"),
|
|
65
|
+
id: "settings"
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
interface Props {
|
|
69
|
+
title: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
activeSection?: string;
|
|
72
|
+
toolbarLeft?: any;
|
|
73
|
+
toolbarRight?: any;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const {
|
|
77
|
+
title,
|
|
78
|
+
description = "Content Management System",
|
|
79
|
+
activeSection = "dashboard",
|
|
80
|
+
toolbarLeft,
|
|
81
|
+
toolbarRight
|
|
82
|
+
} = Astro.props;
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
<ParentLayout title={title} description={description}>
|
|
86
|
+
<RLADashboardGroup>
|
|
87
|
+
<RLADashboardSidebar side="left" collapsible>
|
|
88
|
+
<Fragment slot="header">
|
|
89
|
+
<div class="h-16 min-h-16 flex items-center gap-3 w-full shrink-0" style="height: 4rem; min-height: 4rem; display: flex; align-items: center; gap: 0.75rem;">
|
|
90
|
+
<RLAIcon name="i-lucide-shield" class="size-6 text-primary shrink-0 group-data-[collapsed=true]:hidden" data-dashboard-sidebar-logo />
|
|
91
|
+
<span class="font-semibold text-highlighted truncate flex-1 transition-opacity duration-200 group-data-[collapsed=true]:hidden" data-dashboard-sidebar-label>CMS Admin</span>
|
|
92
|
+
<RLADashboardSidebarCollapse side="left" class="shrink-0 group-data-[collapsed=true]:mx-auto" />
|
|
93
|
+
</div>
|
|
94
|
+
</Fragment>
|
|
95
|
+
|
|
96
|
+
<nav class="flex flex-col gap-4 py-2" data-dashboard-nav-container>
|
|
97
|
+
<!-- Top Dashboard Item -->
|
|
98
|
+
<a
|
|
99
|
+
href={dashboardItem.href}
|
|
100
|
+
data-dashboard-nav
|
|
101
|
+
data-dashboard-section={dashboardItem.id}
|
|
102
|
+
class={
|
|
103
|
+
"flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors group-data-[collapsed=true]:justify-center group-data-[collapsed=true]:px-0 " +
|
|
104
|
+
(dashboardItem.id === activeSection
|
|
105
|
+
? "bg-muted text-highlighted font-semibold"
|
|
106
|
+
: "text-default hover:text-highlighted hover:bg-muted")
|
|
107
|
+
}
|
|
108
|
+
>
|
|
109
|
+
<RLAIcon name={dashboardItem.icon} class="size-5 shrink-0" />
|
|
110
|
+
<span class="truncate flex-1 group-data-[collapsed=true]:hidden" data-dashboard-nav-label>{dashboardItem.label}</span>
|
|
111
|
+
</a>
|
|
112
|
+
|
|
113
|
+
<hr class="border-t border-default/60 mx-3 my-1 group-data-[collapsed=true]:hidden" />
|
|
114
|
+
|
|
115
|
+
<!-- Categorized Sections -->
|
|
116
|
+
{navigationGroups.map((group) => (
|
|
117
|
+
<div class="flex flex-col gap-1">
|
|
118
|
+
<div class="px-3 pt-2 pb-1 text-xs font-semibold text-muted uppercase tracking-wider group-data-[collapsed=true]:hidden">
|
|
119
|
+
{group.title}
|
|
120
|
+
</div>
|
|
121
|
+
<hr class="border-t border-default/40 mx-3 mb-1.5 group-data-[collapsed=true]:hidden" />
|
|
122
|
+
{group.items.map((item) => (
|
|
123
|
+
<a
|
|
124
|
+
href={item.href}
|
|
125
|
+
data-dashboard-nav
|
|
126
|
+
data-dashboard-section={item.id}
|
|
127
|
+
class={
|
|
128
|
+
"flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors group-data-[collapsed=true]:justify-center group-data-[collapsed=true]:px-0 " +
|
|
129
|
+
(item.id === activeSection
|
|
130
|
+
? "bg-muted text-highlighted font-semibold"
|
|
131
|
+
: "text-default hover:text-highlighted hover:bg-muted")
|
|
132
|
+
}
|
|
133
|
+
>
|
|
134
|
+
<RLAIcon name={item.icon} class="size-5 shrink-0" />
|
|
135
|
+
<span class="truncate flex-1 group-data-[collapsed=true]:hidden" data-dashboard-nav-label>{item.label}</span>
|
|
136
|
+
</a>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
))}
|
|
140
|
+
</nav>
|
|
141
|
+
|
|
142
|
+
<Fragment slot="footer">
|
|
143
|
+
<div class="w-full" data-dashboard-nav-container>
|
|
144
|
+
<a
|
|
145
|
+
href={settingsNavItem.href}
|
|
146
|
+
data-dashboard-nav
|
|
147
|
+
data-dashboard-section={settingsNavItem.id}
|
|
148
|
+
class={
|
|
149
|
+
"flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors w-full group-data-[collapsed=true]:justify-center group-data-[collapsed=true]:px-0 " +
|
|
150
|
+
(settingsNavItem.id === activeSection
|
|
151
|
+
? "bg-muted text-highlighted font-semibold"
|
|
152
|
+
: "text-default hover:text-highlighted hover:bg-muted")
|
|
153
|
+
}
|
|
154
|
+
>
|
|
155
|
+
<RLAIcon name={settingsNavItem.icon} class="size-5 shrink-0" />
|
|
156
|
+
<span class="truncate flex-1 group-data-[collapsed=true]:hidden" data-dashboard-nav-label>{settingsNavItem.label}</span>
|
|
157
|
+
</a>
|
|
158
|
+
</div>
|
|
159
|
+
</Fragment>
|
|
160
|
+
</RLADashboardSidebar>
|
|
161
|
+
|
|
162
|
+
<RLADashboardPanel>
|
|
163
|
+
<RLADashboardNavbar slot="header" title={title} icon="i-lucide-file-text" class="h-16 min-h-16">
|
|
164
|
+
<Fragment slot="left">
|
|
165
|
+
<div class="h-16 min-h-16 flex items-center gap-3">
|
|
166
|
+
<RLADashboardSidebarToggle class="flex lg:hidden size-9 rounded-lg items-center justify-center border border-default hover:bg-muted text-toned hover:text-highlighted transition-colors shrink-0" />
|
|
167
|
+
<RLADashboardSidebarCollapse side="left" class="hidden lg:flex size-9 rounded-lg" />
|
|
168
|
+
<h1 class="text-lg sm:text-xl font-semibold text-highlighted truncate" data-dashboard-navbar-title>{title}</h1>
|
|
169
|
+
</div>
|
|
170
|
+
</Fragment>
|
|
171
|
+
<Fragment slot="right">
|
|
172
|
+
<slot name="toolbar-right">{toolbarRight}</slot>
|
|
173
|
+
</Fragment>
|
|
174
|
+
</RLADashboardNavbar>
|
|
175
|
+
|
|
176
|
+
<div slot="body" class="flex flex-col gap-6">
|
|
177
|
+
{(toolbarLeft || Astro.slots.has("toolbar-left")) && (
|
|
178
|
+
<RLADashboardToolbar>
|
|
179
|
+
<Fragment slot="left">
|
|
180
|
+
<slot name="toolbar-left">{toolbarLeft || <p class="text-sm text-muted">{description}</p>}</slot>
|
|
181
|
+
</Fragment>
|
|
182
|
+
</RLADashboardToolbar>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
<div class="section-content flex flex-col gap-6">
|
|
186
|
+
<slot />
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</RLADashboardPanel>
|
|
190
|
+
</RLADashboardGroup>
|
|
191
|
+
</ParentLayout>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
import UIHead from "@rimelight/ui/components/head/UIHead.astro";
|
|
3
|
+
import RLAMain from "@rimelight/ui/components/main/RLAMain.astro";
|
|
4
|
+
import RLAToaster from "@rimelight/ui/components/toaster/RLAToaster.astro";
|
|
5
|
+
import "virtual:uno.css";
|
|
6
|
+
import "../../client/styles/cms-editor.css";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
title?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { title = "Rimelight CMS", description = "Content Management System" } = Astro.props;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<!doctype html>
|
|
17
|
+
<html lang={Astro.currentLocale || "en"} class="dark">
|
|
18
|
+
<head>
|
|
19
|
+
<meta charset="utf-8" />
|
|
20
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
21
|
+
<title>{title}</title>
|
|
22
|
+
<meta name="description" content={description} />
|
|
23
|
+
<UIHead />
|
|
24
|
+
<slot name="head" />
|
|
25
|
+
</head>
|
|
26
|
+
<body class="isolate bg-background text-foreground antialiased selection:bg-primary selection:text-primary-foreground">
|
|
27
|
+
<RLAMain id="main-content" class="dashboard-viewport-area flex flex-col min-w-0">
|
|
28
|
+
<slot />
|
|
29
|
+
</RLAMain>
|
|
30
|
+
<RLAToaster />
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
|
33
|
+
|
|
34
|
+
<style is:global>
|
|
35
|
+
html, body {
|
|
36
|
+
margin: 0;
|
|
37
|
+
padding: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.dashboard-viewport-area {
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
padding-top: 0 !important;
|
|
43
|
+
margin-top: 0 !important;
|
|
44
|
+
height: 100vh !important;
|
|
45
|
+
min-height: 100vh !important;
|
|
46
|
+
max-height: 100vh !important;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
transform: translate(0);
|
|
49
|
+
}
|
|
50
|
+
</style>
|