@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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bylines-Csggkl-g.d.mts +405 -0
  3. package/dist/index.d.mts +724 -0
  4. package/dist/index.mjs +2382 -0
  5. package/dist/integration.d.mts +33 -0
  6. package/dist/integration.mjs +161 -0
  7. package/dist/schema/index.d.mts +2063 -0
  8. package/dist/schema/index.mjs +176 -0
  9. package/dist/schema/sqlite.d.mts +2132 -0
  10. package/dist/schema/sqlite.mjs +137 -0
  11. package/dist/storage/index.d.mts +59 -0
  12. package/dist/storage/index.mjs +133 -0
  13. package/package.json +36 -18
  14. package/src/admin/api/media-file.ts +39 -0
  15. package/src/admin/api/media.ts +224 -0
  16. package/src/admin/layouts/CMSDashboardLayout.astro +191 -0
  17. package/src/admin/layouts/DefaultParentLayout.astro +50 -0
  18. package/src/admin/pages/assets.astro +424 -0
  19. package/src/admin/pages/byline-schema.astro +62 -0
  20. package/src/admin/pages/bylines.astro +16 -0
  21. package/src/admin/pages/categories.astro +21 -0
  22. package/src/admin/pages/content-types.astro +55 -0
  23. package/src/admin/pages/dashboard.astro +61 -0
  24. package/src/admin/pages/pages-edit.astro +595 -0
  25. package/src/admin/pages/pages-index.astro +146 -0
  26. package/src/admin/pages/pages-new.astro +130 -0
  27. package/src/admin/pages/pages-preview.astro +111 -0
  28. package/src/admin/pages/pages-review.astro +236 -0
  29. package/src/admin/pages/settings.astro +203 -0
  30. package/src/admin/pages/tags.astro +21 -0
  31. package/src/admin/pages/templates-edit.astro +234 -0
  32. package/src/admin/pages/templates-index.astro +95 -0
  33. package/src/admin/pages/templates-new.astro +193 -0
  34. package/src/admin/pages/users.astro +74 -0
  35. package/src/admin/pages/versions.astro +74 -0
  36. package/src/astro/BlockRenderer.astro +44 -102
  37. package/src/astro/PageRenderer.astro +12 -5
  38. package/src/astro/blocks/CalloutBlock.astro +7 -7
  39. package/src/astro/blocks/CardBlock.astro +5 -5
  40. package/src/astro/blocks/CodeBlock.astro +2 -2
  41. package/src/astro/blocks/FileTreeBlock.astro +7 -7
  42. package/src/astro/blocks/ImageBlock.astro +2 -2
  43. package/src/astro/blocks/OrderedListBlock.astro +2 -2
  44. package/src/astro/blocks/ParagraphBlock.astro +3 -3
  45. package/src/astro/blocks/SectionBlock.astro +6 -6
  46. package/src/astro/blocks/StepItemBlock.astro +3 -3
  47. package/src/astro/blocks/StepsBlock.astro +1 -1
  48. package/src/astro/blocks/TableBlock.astro +6 -6
  49. package/src/astro/blocks/TabsBlock.astro +9 -9
  50. package/src/astro/blocks/UnorderedListBlock.astro +2 -2
  51. package/src/astro/blocks/index.ts +35 -0
  52. package/src/client/components/RimelightBlock.tsx +19 -19
  53. package/src/client/components/RimelightBlockPicker.tsx +9 -13
  54. package/src/client/components/RimelightBylinesManager.tsx +438 -0
  55. package/src/client/components/RimelightEditor.tsx +3 -3
  56. package/src/client/components/RimelightPreview.tsx +37 -45
  57. package/src/client/components/RimelightPropertiesPanel.tsx +368 -26
  58. package/src/client/components/RimelightTaxonomyManager.tsx +369 -0
  59. package/src/client/components/RimelightTaxonomyMasterDetail.tsx +412 -0
  60. package/src/client/components/RimelightTemplateEditor.tsx +6 -8
  61. package/src/client/components/editors/CalloutEditor.tsx +2 -4
  62. package/src/client/components/editors/CardsEditor.tsx +2 -4
  63. package/src/client/components/editors/CodeEditor.tsx +1 -3
  64. package/src/client/components/editors/FileTreeEditor.tsx +2 -4
  65. package/src/client/components/editors/ImageEditor.tsx +3 -3
  66. package/src/client/components/editors/ParagraphEditor.tsx +2 -2
  67. package/src/client/components/editors/SceneEditor.tsx +6 -12
  68. package/src/client/components/editors/ScriptEditor.tsx +8 -16
  69. package/src/client/components/editors/SectionEditor.tsx +4 -4
  70. package/src/client/components/editors/StepItemEditor.tsx +2 -2
  71. package/src/client/components/editors/StepsEditor.tsx +2 -4
  72. package/src/client/components/editors/TabItemEditor.tsx +3 -3
  73. package/src/client/components/editors/TableEditor.tsx +5 -5
  74. package/src/client/components/editors/TabsEditor.tsx +2 -4
  75. package/src/client/components/index.ts +3 -0
  76. package/src/client/loader.ts +3 -0
  77. package/src/core/diff.ts +145 -0
  78. package/src/core/excerpt.ts +159 -0
  79. package/src/core/preview.ts +87 -0
  80. package/src/core/registry.ts +0 -86
  81. package/src/core/types/blocks.ts +6 -1
  82. package/src/core/types/bylines.ts +14 -0
  83. package/src/core/types/media.ts +13 -0
  84. package/src/core/types/taxonomies.ts +20 -0
  85. package/src/cron/scheduled.ts +57 -0
  86. package/src/docs/agent.ts +116 -0
  87. package/src/docs/config.ts +25 -0
  88. package/src/docs/index.ts +5 -0
  89. package/src/docs/routing.ts +104 -0
  90. package/src/docs/sidebar.ts +251 -0
  91. package/src/docs/toc.ts +47 -0
  92. package/src/env.d.ts +19 -0
  93. package/src/index.ts +57 -12
  94. package/src/integration.ts +250 -0
  95. package/src/loader/live.ts +254 -0
  96. package/src/markdown/index.ts +0 -1
  97. package/src/mcp/index.ts +228 -0
  98. package/src/schema/bylines.ts +23 -0
  99. package/src/schema/index.ts +3 -4
  100. package/src/schema/page_versions.ts +2 -0
  101. package/src/schema/pages.ts +2 -0
  102. package/src/schema/site_settings.ts +41 -0
  103. package/src/schema/sqlite.ts +344 -0
  104. package/src/schema/taxonomies.ts +49 -0
  105. package/src/services/bylines.ts +74 -0
  106. package/src/services/search-indexer.ts +61 -0
  107. package/src/services/site-settings.ts +108 -0
  108. package/src/services/taxonomies.ts +276 -0
  109. package/src/storage/index.ts +256 -0
  110. package/src/astro/blocks/ApiDocumentationBlock.astro +0 -350
  111. package/src/astro/blocks/DialogueBlock.astro +0 -23
  112. package/src/astro/blocks/LivePreviewBlock.astro +0 -27
  113. package/src/astro/blocks/SceneBlock.astro +0 -65
  114. package/src/astro/blocks/ScriptBlock.astro +0 -59
  115. package/src/core/template-sync.ts +0 -75
  116. package/src/markdown/parser.ts +0 -347
  117. package/src/migration-tools.ts +0 -334
  118. package/src/migration.ts +0 -155
  119. package/src/schema/component_dependencies.ts +0 -21
  120. package/src/schema/component_metadata.ts +0 -31
  121. package/src/schema/component_metadata_cache.ts +0 -15
  122. package/src/schema/component_versions.ts +0 -19
  123. package/src/search/sync.ts +0 -32
  124. 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>