@rimelight/cms 0.0.1 → 0.0.3
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/package.json +16 -14
- package/src/admin/api/media-file.ts +39 -0
- package/src/admin/api/media.ts +199 -0
- package/src/admin/layouts/CMSDashboardLayout.astro +178 -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 -96
- package/src/astro/PageRenderer.astro +9 -2
- 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 -39
- package/src/core/types/blocks.ts +83 -6
- 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 +13 -0
- package/src/index.ts +40 -6
- package/src/integration.ts +196 -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 -0
- 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/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 +253 -0
- package/src/astro/blocks/DialogueBlock.astro +0 -23
- 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.ts +0 -155
- package/src/search/sync.ts +0 -32
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rimelight Entertainment
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimelight/cms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Rimelight Entertainment's Content Management System Package",
|
|
6
6
|
"homepage": "https://rimelight.com/docs",
|
|
@@ -33,30 +33,30 @@
|
|
|
33
33
|
"./client/*": "./src/client/*.ts",
|
|
34
34
|
"./auth/*": "./src/auth/*",
|
|
35
35
|
"./cache/*": "./src/cache/*",
|
|
36
|
-
"./search/*": "./src/search/*"
|
|
36
|
+
"./search/*": "./src/search/*",
|
|
37
|
+
"./integration": "./src/integration.ts",
|
|
38
|
+
"./storage": "./src/storage/index.ts",
|
|
39
|
+
"./admin/*": "./src/admin/*"
|
|
37
40
|
},
|
|
38
41
|
"publishConfig": {
|
|
39
42
|
"access": "public"
|
|
40
43
|
},
|
|
41
|
-
"scripts": {
|
|
42
|
-
"check": "pnpm audit --audit-level=moderate && vp check --fix && astro check"
|
|
43
|
-
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@rimelight/i18n": "
|
|
46
|
-
"@rimelight/ui": "
|
|
45
|
+
"@rimelight/i18n": "0.0.6",
|
|
46
|
+
"@rimelight/ui": "0.0.44",
|
|
47
47
|
"drizzle-orm": "0.45.2",
|
|
48
48
|
"solid-js": "1.9.15",
|
|
49
49
|
"sortablejs": "1.15.7"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@astrojs/check": "0.9.10",
|
|
53
|
-
"@rimelight/config": "
|
|
53
|
+
"@rimelight/config": "0.0.3",
|
|
54
54
|
"@types/sortablejs": "1.15.9",
|
|
55
|
-
"astro": "7.2.
|
|
56
|
-
"better-auth": "1.
|
|
55
|
+
"astro": "7.2.7",
|
|
56
|
+
"better-auth": "1.7.2",
|
|
57
57
|
"typescript": "6.0.3",
|
|
58
|
-
"unocss": "66.
|
|
59
|
-
"vitest": "4.1.
|
|
58
|
+
"unocss": "66.8.1",
|
|
59
|
+
"vitest": "4.1.11"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"astro": ">=7.0.0",
|
|
@@ -65,5 +65,7 @@
|
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=26.7.0"
|
|
67
67
|
},
|
|
68
|
-
"
|
|
69
|
-
|
|
68
|
+
"scripts": {
|
|
69
|
+
"check": "vp check --fix && astro check"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { APIRoute } from "astro"
|
|
2
|
+
import { getR2Bucket } from "../../storage/index"
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { storageConfig } from "virtual:rimelight-cms/storage"
|
|
5
|
+
|
|
6
|
+
export const prerender = false
|
|
7
|
+
|
|
8
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
9
|
+
try {
|
|
10
|
+
const bindingName = storageConfig?.binding ?? "BLOB"
|
|
11
|
+
const bucket = await getR2Bucket(locals, bindingName)
|
|
12
|
+
|
|
13
|
+
if (!bucket) {
|
|
14
|
+
return new Response("Storage binding not found.", { status: 500 })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const key = params.key as string
|
|
18
|
+
if (!key) {
|
|
19
|
+
return new Response("Missing file key.", { status: 400 })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const object = await bucket.get(key)
|
|
23
|
+
if (!object) {
|
|
24
|
+
return new Response("File not found in R2 storage.", { status: 404 })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const headers = new Headers()
|
|
28
|
+
object.writeHttpMetadata(headers)
|
|
29
|
+
headers.set("etag", object.httpEtag)
|
|
30
|
+
headers.set("Cache-Control", "public, max-age=31536000, immutable")
|
|
31
|
+
|
|
32
|
+
return new Response(object.body, {
|
|
33
|
+
status: 200,
|
|
34
|
+
headers
|
|
35
|
+
})
|
|
36
|
+
} catch (error: any) {
|
|
37
|
+
return new Response(error.message || "Failed to retrieve file", { status: 500 })
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
|
|
6
|
+
export const prerender = false
|
|
7
|
+
|
|
8
|
+
export const GET: APIRoute = async ({ request, locals }) => {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(request.url)
|
|
11
|
+
const prefix = (url.searchParams.get("prefix") ??
|
|
12
|
+
(storageConfig?.prefix as string | undefined) ??
|
|
13
|
+
"media/") as string
|
|
14
|
+
const cursor = (url.searchParams.get("cursor") || undefined) as string | undefined
|
|
15
|
+
const limit = Number(url.searchParams.get("limit")) || 50
|
|
16
|
+
const search = url.searchParams.get("search")?.toLowerCase()
|
|
17
|
+
const typeFilter = url.searchParams.get("type")?.toLowerCase()
|
|
18
|
+
|
|
19
|
+
const bindingName = (storageConfig?.binding as string | undefined) ?? "BLOB"
|
|
20
|
+
const bucket = getR2Bucket(locals, bindingName)
|
|
21
|
+
|
|
22
|
+
if (!bucket) {
|
|
23
|
+
return new Response(
|
|
24
|
+
JSON.stringify({
|
|
25
|
+
success: false,
|
|
26
|
+
error: `Storage binding "${bindingName}" not found in runtime environment. Ensure R2 bucket is configured in wrangler.jsonc or astro.config.`
|
|
27
|
+
}),
|
|
28
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const apiBasePath = `${url.pathname.replace(/\/$/, "")}/file`
|
|
33
|
+
const result = await listR2Files(bucket, {
|
|
34
|
+
prefix,
|
|
35
|
+
cursor,
|
|
36
|
+
limit,
|
|
37
|
+
publicUrl: storageConfig?.publicUrl as string | undefined,
|
|
38
|
+
apiBasePath
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
let files = result.files
|
|
42
|
+
|
|
43
|
+
if (search) {
|
|
44
|
+
files = files.filter(
|
|
45
|
+
(f) => f.name.toLowerCase().includes(search) || f.key.toLowerCase().includes(search)
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeFilter && typeFilter !== "all") {
|
|
50
|
+
files = files.filter((f) => {
|
|
51
|
+
if (typeFilter === "images") return f.mimeType.startsWith("image/")
|
|
52
|
+
if (typeFilter === "videos") return f.mimeType.startsWith("video/")
|
|
53
|
+
if (typeFilter === "audio") return f.mimeType.startsWith("audio/")
|
|
54
|
+
if (typeFilter === "documents")
|
|
55
|
+
return (
|
|
56
|
+
f.mimeType.includes("pdf") ||
|
|
57
|
+
f.mimeType.includes("text") ||
|
|
58
|
+
f.mimeType.includes("document")
|
|
59
|
+
)
|
|
60
|
+
return true
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return new Response(
|
|
65
|
+
JSON.stringify({
|
|
66
|
+
success: true,
|
|
67
|
+
data: {
|
|
68
|
+
files,
|
|
69
|
+
truncated: result.truncated,
|
|
70
|
+
cursor: result.cursor,
|
|
71
|
+
provider: storageConfig?.provider ?? "r2",
|
|
72
|
+
binding: bindingName
|
|
73
|
+
}
|
|
74
|
+
}),
|
|
75
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
76
|
+
)
|
|
77
|
+
} catch (error: any) {
|
|
78
|
+
return new Response(
|
|
79
|
+
JSON.stringify({
|
|
80
|
+
success: false,
|
|
81
|
+
error: error.message || "Failed to list media files"
|
|
82
|
+
}),
|
|
83
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
89
|
+
try {
|
|
90
|
+
const bindingName = storageConfig?.binding ?? "BLOB"
|
|
91
|
+
const bucket = getR2Bucket(locals, bindingName)
|
|
92
|
+
|
|
93
|
+
if (!bucket) {
|
|
94
|
+
return new Response(
|
|
95
|
+
JSON.stringify({
|
|
96
|
+
success: false,
|
|
97
|
+
error: `Storage binding "${bindingName}" not found in runtime environment.`
|
|
98
|
+
}),
|
|
99
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const contentType = request.headers.get("content-type") || ""
|
|
104
|
+
if (!contentType.includes("multipart/form-data")) {
|
|
105
|
+
return new Response(
|
|
106
|
+
JSON.stringify({ success: false, error: "Expected multipart/form-data content type." }),
|
|
107
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const formData = await request.formData()
|
|
112
|
+
const file = formData.get("file")
|
|
113
|
+
|
|
114
|
+
if (!file || !(file instanceof File)) {
|
|
115
|
+
return new Response(
|
|
116
|
+
JSON.stringify({
|
|
117
|
+
success: false,
|
|
118
|
+
error: "No file found in form data payload (field name 'file')."
|
|
119
|
+
}),
|
|
120
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const url = new URL(request.url)
|
|
125
|
+
const apiBasePath = `${url.pathname.replace(/\/$/, "")}/file`
|
|
126
|
+
|
|
127
|
+
const uploaded = await uploadR2File(bucket, file, {
|
|
128
|
+
prefix: (storageConfig?.prefix as string | undefined) ?? "media/",
|
|
129
|
+
publicUrl: storageConfig?.publicUrl as string | undefined,
|
|
130
|
+
apiBasePath
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return new Response(
|
|
134
|
+
JSON.stringify({
|
|
135
|
+
success: true,
|
|
136
|
+
data: { file: uploaded }
|
|
137
|
+
}),
|
|
138
|
+
{ status: 201, headers: { "Content-Type": "application/json" } }
|
|
139
|
+
)
|
|
140
|
+
} catch (error: any) {
|
|
141
|
+
return new Response(
|
|
142
|
+
JSON.stringify({
|
|
143
|
+
success: false,
|
|
144
|
+
error: error.message || "Failed to upload file to R2"
|
|
145
|
+
}),
|
|
146
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const DELETE: APIRoute = async ({ request, locals }) => {
|
|
152
|
+
try {
|
|
153
|
+
const bindingName = storageConfig?.binding ?? "BLOB"
|
|
154
|
+
const bucket = getR2Bucket(locals, bindingName)
|
|
155
|
+
|
|
156
|
+
if (!bucket) {
|
|
157
|
+
return new Response(
|
|
158
|
+
JSON.stringify({
|
|
159
|
+
success: false,
|
|
160
|
+
error: `Storage binding "${bindingName}" not found in runtime environment.`
|
|
161
|
+
}),
|
|
162
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let key = ""
|
|
167
|
+
const url = new URL(request.url)
|
|
168
|
+
const queryKey = url.searchParams.get("key")
|
|
169
|
+
|
|
170
|
+
if (queryKey) {
|
|
171
|
+
key = queryKey
|
|
172
|
+
} else {
|
|
173
|
+
const body = await request.json().catch(() => ({}))
|
|
174
|
+
key = body.key
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!key) {
|
|
178
|
+
return new Response(
|
|
179
|
+
JSON.stringify({ success: false, error: "Missing required 'key' parameter for deletion." }),
|
|
180
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
await deleteR2File(bucket, key)
|
|
185
|
+
|
|
186
|
+
return new Response(
|
|
187
|
+
JSON.stringify({ success: true, message: `File "${key}" deleted successfully.` }),
|
|
188
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
189
|
+
)
|
|
190
|
+
} catch (error: any) {
|
|
191
|
+
return new Response(
|
|
192
|
+
JSON.stringify({
|
|
193
|
+
success: false,
|
|
194
|
+
error: error.message || "Failed to delete file from R2"
|
|
195
|
+
}),
|
|
196
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
import ParentLayout from "virtual:rimelight-cms/parent-layout";
|
|
3
|
+
import RLADashboardGroup from "@rimelight/ui/components/dashboard-group/RLADashboardGroup.astro";
|
|
4
|
+
import RLADashboardSidebar from "@rimelight/ui/components/dashboard-sidebar/RLADashboardSidebar.astro";
|
|
5
|
+
import RLADashboardNavbar from "@rimelight/ui/components/dashboard-navbar/RLADashboardNavbar.astro";
|
|
6
|
+
import RLADashboardPanel from "@rimelight/ui/components/dashboard-panel/RLADashboardPanel.astro";
|
|
7
|
+
import RLADashboardToolbar from "@rimelight/ui/components/dashboard-toolbar/RLADashboardToolbar.astro";
|
|
8
|
+
import RLADashboardSidebarCollapse from "@rimelight/ui/components/dashboard-sidebar-collapse/RLADashboardSidebarCollapse.astro";
|
|
9
|
+
import RLAIcon from "@rimelight/ui/components/icon/RLAIcon.astro";
|
|
10
|
+
import { getRelativeLocaleUrl } from "@rimelight/i18n";
|
|
11
|
+
import "../../client/styles/cms-editor.css";
|
|
12
|
+
|
|
13
|
+
const currentLocale = Astro.currentLocale ?? "en";
|
|
14
|
+
|
|
15
|
+
const dashboardItem = {
|
|
16
|
+
icon: "i-lucide-layout-dashboard",
|
|
17
|
+
label: "Dashboard",
|
|
18
|
+
href: getRelativeLocaleUrl(currentLocale, "/cms"),
|
|
19
|
+
id: "dashboard"
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const navigationGroups = [
|
|
23
|
+
{
|
|
24
|
+
title: "Collections",
|
|
25
|
+
items: [
|
|
26
|
+
{ icon: "i-lucide-file-text", label: "Pages", href: getRelativeLocaleUrl(currentLocale, "/cms/pages"), id: "pages" },
|
|
27
|
+
{ icon: "i-lucide-layout-template", label: "Templates", href: getRelativeLocaleUrl(currentLocale, "/cms/templates"), id: "templates" },
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
title: "Manage",
|
|
32
|
+
items: [
|
|
33
|
+
{ icon: "i-lucide-image", label: "Assets", href: getRelativeLocaleUrl(currentLocale, "/cms/assets"), id: "assets" },
|
|
34
|
+
{ icon: "i-lucide-folder", label: "Categories", href: getRelativeLocaleUrl(currentLocale, "/cms/categories"), id: "categories" },
|
|
35
|
+
{ icon: "i-lucide-tag", label: "Tags", href: getRelativeLocaleUrl(currentLocale, "/cms/tags"), id: "tags" },
|
|
36
|
+
{ icon: "i-lucide-pen-tool", label: "Bylines", href: getRelativeLocaleUrl(currentLocale, "/cms/bylines"), id: "bylines" },
|
|
37
|
+
{ icon: "i-lucide-git-branch", label: "Versions", href: getRelativeLocaleUrl(currentLocale, "/cms/versions"), id: "versions" },
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: "Admin",
|
|
42
|
+
items: [
|
|
43
|
+
{ icon: "i-lucide-database", label: "Content Types", href: getRelativeLocaleUrl(currentLocale, "/cms/content-types"), id: "content-types" },
|
|
44
|
+
{ icon: "i-lucide-file-cog", label: "Byline Schema", href: getRelativeLocaleUrl(currentLocale, "/cms/byline-schema"), id: "byline-schema" },
|
|
45
|
+
{ icon: "i-lucide-users", label: "Users", href: getRelativeLocaleUrl(currentLocale, "/cms/users"), id: "users" },
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const settingsNavItem = {
|
|
51
|
+
icon: "i-lucide-settings",
|
|
52
|
+
label: "Settings",
|
|
53
|
+
href: getRelativeLocaleUrl(currentLocale, "/cms/settings"),
|
|
54
|
+
id: "settings"
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
interface Props {
|
|
58
|
+
title: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
activeSection?: string;
|
|
61
|
+
toolbarLeft?: any;
|
|
62
|
+
toolbarRight?: any;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const {
|
|
66
|
+
title,
|
|
67
|
+
description = "Content Management System",
|
|
68
|
+
activeSection = "dashboard",
|
|
69
|
+
toolbarLeft,
|
|
70
|
+
toolbarRight
|
|
71
|
+
} = Astro.props;
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
<ParentLayout title={title} description={description}>
|
|
75
|
+
<RLADashboardGroup>
|
|
76
|
+
<RLADashboardSidebar side="left" collapsible>
|
|
77
|
+
<Fragment slot="header">
|
|
78
|
+
<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;">
|
|
79
|
+
<RLAIcon name="i-lucide-shield" class="size-6 text-primary shrink-0 group-data-[collapsed=true]:hidden" data-dashboard-sidebar-logo />
|
|
80
|
+
<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>
|
|
81
|
+
<RLADashboardSidebarCollapse side="left" class="shrink-0 group-data-[collapsed=true]:mx-auto" />
|
|
82
|
+
</div>
|
|
83
|
+
</Fragment>
|
|
84
|
+
|
|
85
|
+
<nav class="flex flex-col gap-4 py-2" data-dashboard-nav-container>
|
|
86
|
+
<!-- Top Dashboard Item -->
|
|
87
|
+
<a
|
|
88
|
+
href={dashboardItem.href}
|
|
89
|
+
data-dashboard-nav
|
|
90
|
+
data-dashboard-section={dashboardItem.id}
|
|
91
|
+
class={
|
|
92
|
+
"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 " +
|
|
93
|
+
(dashboardItem.id === activeSection
|
|
94
|
+
? "bg-muted text-highlighted font-semibold"
|
|
95
|
+
: "text-default hover:text-highlighted hover:bg-muted")
|
|
96
|
+
}
|
|
97
|
+
>
|
|
98
|
+
<RLAIcon name={dashboardItem.icon} class="size-5 shrink-0" />
|
|
99
|
+
<span class="truncate flex-1 group-data-[collapsed=true]:hidden" data-dashboard-nav-label>{dashboardItem.label}</span>
|
|
100
|
+
</a>
|
|
101
|
+
|
|
102
|
+
<hr class="border-t border-default/60 mx-3 my-1 group-data-[collapsed=true]:hidden" />
|
|
103
|
+
|
|
104
|
+
<!-- Categorized Sections -->
|
|
105
|
+
{navigationGroups.map((group) => (
|
|
106
|
+
<div class="flex flex-col gap-1">
|
|
107
|
+
<div class="px-3 pt-2 pb-1 text-xs font-semibold text-muted uppercase tracking-wider group-data-[collapsed=true]:hidden">
|
|
108
|
+
{group.title}
|
|
109
|
+
</div>
|
|
110
|
+
<hr class="border-t border-default/40 mx-3 mb-1.5 group-data-[collapsed=true]:hidden" />
|
|
111
|
+
{group.items.map((item) => (
|
|
112
|
+
<a
|
|
113
|
+
href={item.href}
|
|
114
|
+
data-dashboard-nav
|
|
115
|
+
data-dashboard-section={item.id}
|
|
116
|
+
class={
|
|
117
|
+
"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 " +
|
|
118
|
+
(item.id === activeSection
|
|
119
|
+
? "bg-muted text-highlighted font-semibold"
|
|
120
|
+
: "text-default hover:text-highlighted hover:bg-muted")
|
|
121
|
+
}
|
|
122
|
+
>
|
|
123
|
+
<RLAIcon name={item.icon} class="size-5 shrink-0" />
|
|
124
|
+
<span class="truncate flex-1 group-data-[collapsed=true]:hidden" data-dashboard-nav-label>{item.label}</span>
|
|
125
|
+
</a>
|
|
126
|
+
))}
|
|
127
|
+
</div>
|
|
128
|
+
))}
|
|
129
|
+
</nav>
|
|
130
|
+
|
|
131
|
+
<Fragment slot="footer">
|
|
132
|
+
<div class="w-full" data-dashboard-nav-container>
|
|
133
|
+
<a
|
|
134
|
+
href={settingsNavItem.href}
|
|
135
|
+
data-dashboard-nav
|
|
136
|
+
data-dashboard-section={settingsNavItem.id}
|
|
137
|
+
class={
|
|
138
|
+
"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 " +
|
|
139
|
+
(settingsNavItem.id === activeSection
|
|
140
|
+
? "bg-muted text-highlighted font-semibold"
|
|
141
|
+
: "text-default hover:text-highlighted hover:bg-muted")
|
|
142
|
+
}
|
|
143
|
+
>
|
|
144
|
+
<RLAIcon name={settingsNavItem.icon} class="size-5 shrink-0" />
|
|
145
|
+
<span class="truncate flex-1 group-data-[collapsed=true]:hidden" data-dashboard-nav-label>{settingsNavItem.label}</span>
|
|
146
|
+
</a>
|
|
147
|
+
</div>
|
|
148
|
+
</Fragment>
|
|
149
|
+
</RLADashboardSidebar>
|
|
150
|
+
|
|
151
|
+
<RLADashboardPanel>
|
|
152
|
+
<RLADashboardNavbar slot="header" title={title} icon="i-lucide-file-text" style="height: 4rem; min-height: 4rem;">
|
|
153
|
+
<Fragment slot="left">
|
|
154
|
+
<div class="h-16 min-h-16 flex items-center gap-3" style="height: 4rem; min-height: 4rem; display: flex; align-items: center;">
|
|
155
|
+
<h1 class="text-xl font-semibold text-highlighted" data-dashboard-navbar-title>{title}</h1>
|
|
156
|
+
</div>
|
|
157
|
+
</Fragment>
|
|
158
|
+
<Fragment slot="right">
|
|
159
|
+
<slot name="toolbar-right">{toolbarRight}</slot>
|
|
160
|
+
</Fragment>
|
|
161
|
+
</RLADashboardNavbar>
|
|
162
|
+
|
|
163
|
+
<div slot="body" class="flex flex-col gap-6">
|
|
164
|
+
{(toolbarLeft || Astro.slots.has("toolbar-left")) && (
|
|
165
|
+
<RLADashboardToolbar>
|
|
166
|
+
<Fragment slot="left">
|
|
167
|
+
<slot name="toolbar-left">{toolbarLeft || <p class="text-sm text-muted">{description}</p>}</slot>
|
|
168
|
+
</Fragment>
|
|
169
|
+
</RLADashboardToolbar>
|
|
170
|
+
)}
|
|
171
|
+
|
|
172
|
+
<div class="section-content flex flex-col gap-6">
|
|
173
|
+
<slot />
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</RLADashboardPanel>
|
|
177
|
+
</RLADashboardGroup>
|
|
178
|
+
</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>
|