@rimelight/cms 0.0.9 → 0.0.11
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/dist/index.d.mts +1832 -16
- package/dist/index.mjs +1534 -218
- package/dist/schema/index.d.mts +257 -0
- package/dist/schema/index.mjs +18 -2
- package/dist/schema/sqlite.d.mts +232 -0
- package/dist/schema/sqlite.mjs +15 -1
- package/package.json +22 -11
- package/src/admin/api/media-file.ts +0 -1
- package/src/admin/api/media.ts +0 -2
- package/src/admin/layouts/CMSDashboardLayout.astro +7 -1
- package/src/admin/layouts/DefaultParentLayout.astro +0 -1
- package/src/admin/pages/pages-edit.astro +4 -1
- package/src/admin/pages/pages-index.astro +262 -101
- package/src/admin/pages/pages-preview.astro +5 -2
- package/src/admin/pages/templates-index.astro +224 -54
- package/src/admin/pages/users.astro +96 -50
- package/src/admin/pages/versions.astro +49 -37
- package/src/astro/blocks/DialogueBlock.astro +25 -0
- package/src/astro/blocks/LivePreviewBlock.astro +33 -0
- package/src/astro/blocks/SceneBlock.astro +39 -0
- package/src/astro/blocks/ScriptBlock.astro +77 -0
- package/src/astro/blocks/TableBlock.astro +30 -37
- package/src/astro/blocks/index.ts +9 -1
- package/src/env.d.ts +7 -1
- package/src/loader/live.ts +1 -2
- package/src/mcp/index.ts +1290 -32
- package/src/schema/content_types.ts +59 -0
- package/src/schema/index.ts +1 -0
- package/src/schema/sqlite.ts +35 -0
- package/src/services/search-indexer.ts +20 -9
- package/src/services/site-settings.ts +18 -14
- package/src/services/template-seeder.ts +249 -0
- package/src/storage/index.ts +0 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
import CMSDashboardLayout from "../layouts/CMSDashboardLayout.astro";
|
|
3
3
|
import RLAButton from "@rimelight/ui/components/button/RLAButton.astro";
|
|
4
|
-
import
|
|
4
|
+
import RLATable from "@rimelight/ui/components/table/RLATable.astro";
|
|
5
|
+
import type { TableColumn, TableRow } from "@rimelight/ui/components/table/table.ts";
|
|
5
6
|
import { db } from "virtual:rimelight-cms/db";
|
|
6
7
|
import { pageTemplates } from "../../schema/page_templates.ts";
|
|
7
8
|
import { desc } from "drizzle-orm";
|
|
8
9
|
import { getRelativeLocaleUrl } from "@rimelight/i18n";
|
|
10
|
+
import { ensureDefaultTemplates } from "../../services/template-seeder.ts";
|
|
9
11
|
|
|
10
12
|
export const prerender = false;
|
|
11
13
|
|
|
@@ -14,6 +16,9 @@ const currentLocale = Astro.currentLocale ?? "en";
|
|
|
14
16
|
let templates: any[] = [];
|
|
15
17
|
if (db) {
|
|
16
18
|
try {
|
|
19
|
+
// Auto-seed default starter templates if table is empty
|
|
20
|
+
await ensureDefaultTemplates(db, pageTemplates);
|
|
21
|
+
|
|
17
22
|
templates = await db
|
|
18
23
|
.select()
|
|
19
24
|
.from(pageTemplates)
|
|
@@ -22,74 +27,239 @@ if (db) {
|
|
|
22
27
|
console.warn("Failed to fetch templates:", err);
|
|
23
28
|
}
|
|
24
29
|
}
|
|
30
|
+
|
|
31
|
+
// Format template rows
|
|
32
|
+
const formattedTemplates = templates.map((tmpl) => {
|
|
33
|
+
let titleStr = "";
|
|
34
|
+
if (typeof tmpl.title === "object" && tmpl.title !== null) {
|
|
35
|
+
const titleObj = tmpl.title as Record<string, string>;
|
|
36
|
+
titleStr = titleObj[currentLocale] || titleObj["en"] || (Object.values(titleObj)[0] as string | undefined) || "";
|
|
37
|
+
} else {
|
|
38
|
+
titleStr = String(tmpl.title || "Untitled Template");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let descStr = "";
|
|
42
|
+
if (typeof tmpl.description === "object" && tmpl.description !== null) {
|
|
43
|
+
descStr = (tmpl.description as Record<string, string>)[currentLocale] || (tmpl.description as Record<string, string>)["en"] || "";
|
|
44
|
+
} else if (tmpl.description) {
|
|
45
|
+
descStr = String(tmpl.description);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const pageType = String(tmpl.pageType || "page").toLowerCase();
|
|
49
|
+
const initialBlocksCount = Array.isArray(tmpl.initialBlocks) ? tmpl.initialBlocks.length : 0;
|
|
50
|
+
const propertiesCount = tmpl.defaultProperties ? Object.keys(tmpl.defaultProperties).length : 0;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
id: tmpl.id,
|
|
54
|
+
slug: tmpl.slug || "",
|
|
55
|
+
title: titleStr,
|
|
56
|
+
description: descStr,
|
|
57
|
+
pageType,
|
|
58
|
+
version: tmpl.version || 1,
|
|
59
|
+
initialBlocksCount,
|
|
60
|
+
propertiesCount,
|
|
61
|
+
createdAt: tmpl.createdAt ? new Date(tmpl.createdAt) : new Date(),
|
|
62
|
+
updatedAt: tmpl.updatedAt ? new Date(tmpl.updatedAt) : new Date()
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const typeCounts: Record<string, number> = {};
|
|
67
|
+
for (const t of formattedTemplates) {
|
|
68
|
+
typeCounts[t.pageType] = (typeCounts[t.pageType] || 0) + 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getTypeBadgeColor(type: string): "primary" | "secondary" | "success" | "warning" | "error" | "neutral" {
|
|
72
|
+
switch (type) {
|
|
73
|
+
case "blog":
|
|
74
|
+
return "primary";
|
|
75
|
+
case "doc":
|
|
76
|
+
case "documentation":
|
|
77
|
+
return "secondary";
|
|
78
|
+
case "legal":
|
|
79
|
+
case "policy":
|
|
80
|
+
return "warning";
|
|
81
|
+
case "wiki":
|
|
82
|
+
case "species":
|
|
83
|
+
return "success";
|
|
84
|
+
case "character":
|
|
85
|
+
case "hero":
|
|
86
|
+
return "primary";
|
|
87
|
+
case "series":
|
|
88
|
+
return "secondary";
|
|
89
|
+
case "item":
|
|
90
|
+
case "location":
|
|
91
|
+
return "warning";
|
|
92
|
+
case "patchnote":
|
|
93
|
+
case "announcement":
|
|
94
|
+
return "error";
|
|
95
|
+
default:
|
|
96
|
+
return "neutral";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getTypeIcon(type: string): string {
|
|
101
|
+
switch (type) {
|
|
102
|
+
case "blog":
|
|
103
|
+
return "i-lucide-newspaper";
|
|
104
|
+
case "doc":
|
|
105
|
+
case "documentation":
|
|
106
|
+
return "i-lucide-book-open";
|
|
107
|
+
case "character":
|
|
108
|
+
case "hero":
|
|
109
|
+
return "i-lucide-user";
|
|
110
|
+
case "series":
|
|
111
|
+
return "i-lucide-clapperboard";
|
|
112
|
+
case "location":
|
|
113
|
+
return "i-lucide-map-pin";
|
|
114
|
+
case "item":
|
|
115
|
+
return "i-lucide-package";
|
|
116
|
+
case "species":
|
|
117
|
+
return "i-lucide-dna";
|
|
118
|
+
case "skill":
|
|
119
|
+
return "i-lucide-sparkles";
|
|
120
|
+
case "patchnote":
|
|
121
|
+
return "i-lucide-file-code";
|
|
122
|
+
default:
|
|
123
|
+
return "i-lucide-layout-template";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const templateColumns: TableColumn<any>[] = [
|
|
128
|
+
{
|
|
129
|
+
accessorKey: "title",
|
|
130
|
+
header: "Template & Slug",
|
|
131
|
+
cell: ({ row }: { row: TableRow<any> }) => {
|
|
132
|
+
const tmpl = row.original;
|
|
133
|
+
return `<div class="flex items-start gap-3 py-1">
|
|
134
|
+
<div class="mt-0.5 size-8 rounded-lg bg-elevated border border-default text-muted flex items-center justify-center shrink-0">
|
|
135
|
+
<span class="${getTypeIcon(tmpl.pageType)} size-4"></span>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="min-w-0 flex-1">
|
|
138
|
+
<a href="${getRelativeLocaleUrl(currentLocale, `/cms/templates/${tmpl.id}/edit`)}" class="font-semibold text-highlighted hover:text-primary transition-colors text-sm truncate block" title="${tmpl.title}">
|
|
139
|
+
${tmpl.title}
|
|
140
|
+
</a>
|
|
141
|
+
<div class="flex items-center gap-2 mt-1">
|
|
142
|
+
<span class="font-mono text-xs text-muted bg-elevated px-1.5 py-0.5 rounded border border-default/60 truncate max-w-xs">
|
|
143
|
+
${tmpl.slug}
|
|
144
|
+
</span>
|
|
145
|
+
</div>
|
|
146
|
+
${tmpl.description ? `<p class="text-xs text-muted/80 mt-1 line-clamp-1 max-w-md">${tmpl.description}</p>` : ''}
|
|
147
|
+
</div>
|
|
148
|
+
</div>`;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
accessorKey: "pageType",
|
|
153
|
+
header: "Page Type",
|
|
154
|
+
size: 140,
|
|
155
|
+
cell: ({ row }: { row: TableRow<any> }) => {
|
|
156
|
+
const tmpl = row.original;
|
|
157
|
+
const color = getTypeBadgeColor(tmpl.pageType);
|
|
158
|
+
return `<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase tracking-wider bg-${color}-500/10 text-${color}-500 border border-${color}-500/20">${tmpl.pageType}</span>`;
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: "specs",
|
|
163
|
+
header: "Schema Specs",
|
|
164
|
+
size: 160,
|
|
165
|
+
cell: ({ row }: { row: TableRow<any> }) => {
|
|
166
|
+
const tmpl = row.original;
|
|
167
|
+
return `<div class="flex items-center gap-2 text-xs text-muted font-mono">
|
|
168
|
+
<span>${tmpl.initialBlocksCount} ${tmpl.initialBlocksCount === 1 ? "block" : "blocks"}</span>
|
|
169
|
+
<span class="opacity-40">•</span>
|
|
170
|
+
<span>${tmpl.propertiesCount} ${tmpl.propertiesCount === 1 ? "prop" : "props"}</span>
|
|
171
|
+
</div>`;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
accessorKey: "version",
|
|
176
|
+
header: "Version",
|
|
177
|
+
size: 100,
|
|
178
|
+
cell: ({ row }: { row: TableRow<any> }) => `<span class="px-2 py-0.5 rounded bg-elevated border border-default text-highlighted font-semibold text-xs font-mono">v${row.getValue("version")}</span>`
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "actions",
|
|
182
|
+
actions: (row: TableRow<any>) => [
|
|
183
|
+
{
|
|
184
|
+
label: "Create Page",
|
|
185
|
+
icon: "i-lucide-plus",
|
|
186
|
+
to: getRelativeLocaleUrl(currentLocale, `/cms/pages/new?templateId=${row.original.id}`)
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
label: "Edit Template",
|
|
190
|
+
icon: "i-lucide-pencil",
|
|
191
|
+
to: getRelativeLocaleUrl(currentLocale, `/cms/templates/${row.original.id}/edit`)
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
type: "separator" as const
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
label: "Delete Template",
|
|
198
|
+
icon: "i-lucide-trash-2",
|
|
199
|
+
color: "error" as const,
|
|
200
|
+
id: `delete-tmpl-${row.original.id}`
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
];
|
|
25
205
|
---
|
|
26
206
|
|
|
27
|
-
<CMSDashboardLayout title="Templates" description="Manage
|
|
207
|
+
<CMSDashboardLayout title="Templates" description="Manage page templates, property schemas, and starter blocks" activeSection="templates">
|
|
28
208
|
<Fragment slot="toolbar-right">
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
209
|
+
<div class="flex items-center gap-2">
|
|
210
|
+
<RLAButton
|
|
211
|
+
href={getRelativeLocaleUrl(currentLocale, "/cms/pages")}
|
|
212
|
+
variant="outline"
|
|
213
|
+
color="neutral"
|
|
214
|
+
size="sm"
|
|
215
|
+
leadingIcon="i-lucide-files"
|
|
216
|
+
>
|
|
217
|
+
Pages
|
|
218
|
+
</RLAButton>
|
|
219
|
+
<RLAButton
|
|
220
|
+
href={getRelativeLocaleUrl(currentLocale, "/cms/templates/new")}
|
|
221
|
+
variant="solid"
|
|
222
|
+
color="primary"
|
|
223
|
+
size="sm"
|
|
224
|
+
leadingIcon="i-lucide-plus"
|
|
225
|
+
>
|
|
226
|
+
Create Template
|
|
227
|
+
</RLAButton>
|
|
228
|
+
</div>
|
|
32
229
|
</Fragment>
|
|
33
230
|
|
|
34
|
-
<div class="
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{templates.map((template: any) => (
|
|
46
|
-
<tr class="hover:bg-elevated/50 transition">
|
|
47
|
-
<td class="px-6 py-4 whitespace-nowrap">
|
|
48
|
-
<div class="text-sm font-medium text-highlighted">{template.title}</div>
|
|
49
|
-
<div class="text-xs font-mono text-muted">{template.slug}</div>
|
|
50
|
-
</td>
|
|
51
|
-
<td class="px-6 py-4 whitespace-nowrap">
|
|
52
|
-
<RLABadge variant="soft" color="primary" size="xs">{template.pageType}</RLABadge>
|
|
53
|
-
</td>
|
|
54
|
-
<td class="px-6 py-4 whitespace-nowrap text-xs font-mono text-muted">
|
|
55
|
-
v{template.version}
|
|
56
|
-
</td>
|
|
57
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-right flex items-center justify-end gap-2">
|
|
58
|
-
<RLAButton href={getRelativeLocaleUrl(currentLocale, `/cms/pages/new?templateId=${template.id}`)} variant="ghost" color="primary" size="xs">
|
|
59
|
-
Create Page
|
|
60
|
-
</RLAButton>
|
|
61
|
-
<RLAButton href={getRelativeLocaleUrl(currentLocale, `/cms/templates/${template.id}/edit`)} variant="ghost" color="neutral" size="xs">
|
|
62
|
-
Edit
|
|
63
|
-
</RLAButton>
|
|
64
|
-
<RLAButton variant="ghost" color="error" size="xs" class="delete-template-btn" data-id={template.id}>
|
|
65
|
-
Delete
|
|
66
|
-
</RLAButton>
|
|
67
|
-
</td>
|
|
68
|
-
</tr>
|
|
69
|
-
))}
|
|
70
|
-
</tbody>
|
|
71
|
-
</table>
|
|
231
|
+
<div class="flex flex-col gap-4">
|
|
232
|
+
<RLATable
|
|
233
|
+
data={formattedTemplates}
|
|
234
|
+
columns={templateColumns}
|
|
235
|
+
striped
|
|
236
|
+
hoverable
|
|
237
|
+
searchable
|
|
238
|
+
showColumnsToggle
|
|
239
|
+
exportable
|
|
240
|
+
exportFilename="rimelight-templates-export"
|
|
241
|
+
/>
|
|
72
242
|
</div>
|
|
73
243
|
|
|
74
244
|
<script>
|
|
75
|
-
document.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (!
|
|
81
|
-
|
|
245
|
+
document.addEventListener("click", async (e) => {
|
|
246
|
+
const target = e.target as HTMLElement;
|
|
247
|
+
const deleteItem = target.closest<HTMLElement>("[data-item-id^='delete-tmpl-']");
|
|
248
|
+
if (deleteItem) {
|
|
249
|
+
const templateId = deleteItem.getAttribute("data-item-id")?.replace("delete-tmpl-", "") || "";
|
|
250
|
+
if (!templateId) return;
|
|
251
|
+
if (!confirm("Are you sure you want to delete this template? This will not delete existing pages created with this template.")) return;
|
|
82
252
|
try {
|
|
83
|
-
const res = await fetch(`/api/cms/templates/${
|
|
253
|
+
const res = await fetch(`/api/cms/templates/${templateId}`, { method: "DELETE" });
|
|
84
254
|
if (res.ok) {
|
|
85
255
|
window.location.reload();
|
|
86
256
|
} else {
|
|
87
|
-
alert(
|
|
257
|
+
alert("Failed to delete template");
|
|
88
258
|
}
|
|
89
259
|
} catch {
|
|
90
|
-
alert(
|
|
260
|
+
alert("Network error deleting template");
|
|
91
261
|
}
|
|
92
|
-
}
|
|
262
|
+
}
|
|
93
263
|
});
|
|
94
264
|
</script>
|
|
95
265
|
</CMSDashboardLayout>
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
import CMSDashboardLayout from "../layouts/CMSDashboardLayout.astro";
|
|
3
3
|
import RLAButton from "@rimelight/ui/components/button/RLAButton.astro";
|
|
4
|
-
import RLABadge from "@rimelight/ui/components/badge/RLABadge.astro";
|
|
5
|
-
import RLAAvatar from "@rimelight/ui/components/avatar/RLAAvatar.astro";
|
|
6
4
|
import RLATable from "@rimelight/ui/components/table/RLATable.astro";
|
|
7
|
-
import type { TableColumn } from "@rimelight/ui/components/table/table.ts";
|
|
5
|
+
import type { TableColumn, TableRow } from "@rimelight/ui/components/table/table.ts";
|
|
8
6
|
|
|
9
7
|
export const prerender = false;
|
|
10
8
|
|
|
@@ -15,60 +13,108 @@ const users = [
|
|
|
15
13
|
{ id: "usr_4", name: "Elena Vance", email: "elena@rimelight.com", role: "Reviewer", status: "invited", lastActive: "Never" },
|
|
16
14
|
];
|
|
17
15
|
|
|
18
|
-
const
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
const userColumns: TableColumn<any>[] = [
|
|
17
|
+
{
|
|
18
|
+
accessorKey: "name",
|
|
19
|
+
header: "User",
|
|
20
|
+
cell: ({ row }: { row: TableRow<any> }) => {
|
|
21
|
+
const user = row.original;
|
|
22
|
+
return `<div class="flex items-center gap-3 py-1">
|
|
23
|
+
<div class="size-8 rounded-full bg-primary/10 text-primary font-bold text-xs flex items-center justify-center border border-primary/20 shrink-0">
|
|
24
|
+
${user.name.slice(0, 2).toUpperCase()}
|
|
25
|
+
</div>
|
|
26
|
+
<div class="flex flex-col min-w-0">
|
|
27
|
+
<span class="font-medium text-highlighted text-sm truncate">${user.name}</span>
|
|
28
|
+
<span class="text-xs text-muted font-normal truncate">${user.email}</span>
|
|
29
|
+
</div>
|
|
30
|
+
</div>`;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
accessorKey: "role",
|
|
35
|
+
header: "Role",
|
|
36
|
+
size: 150,
|
|
37
|
+
cell: ({ row }: { row: TableRow<any> }) => {
|
|
38
|
+
const user = row.original;
|
|
39
|
+
const roleColor = user.role === "Super Admin" ? "primary" : user.role === "Editor" ? "secondary" : "neutral";
|
|
40
|
+
return `<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border ${
|
|
41
|
+
roleColor === 'primary' ? 'bg-primary/10 text-primary border-primary/20' :
|
|
42
|
+
roleColor === 'secondary' ? 'bg-secondary/10 text-secondary border-secondary/20' :
|
|
43
|
+
'bg-muted/10 text-muted border-default'
|
|
44
|
+
}">${user.role}</span>`;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
accessorKey: "status",
|
|
49
|
+
header: "Status",
|
|
50
|
+
size: 130,
|
|
51
|
+
cell: ({ row }: { row: TableRow<any> }) => {
|
|
52
|
+
const user = row.original;
|
|
53
|
+
if (user.status === "active") {
|
|
54
|
+
return `<div class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-emerald-500/10 text-emerald-500 border border-emerald-500/20">
|
|
55
|
+
<span class="size-1.5 rounded-full bg-emerald-500"></span>
|
|
56
|
+
<span>Active</span>
|
|
57
|
+
</div>`;
|
|
58
|
+
}
|
|
59
|
+
return `<div class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-amber-500/10 text-amber-500 border border-amber-500/20">
|
|
60
|
+
<span class="size-1.5 rounded-full bg-amber-500"></span>
|
|
61
|
+
<span>Invited</span>
|
|
62
|
+
</div>`;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
accessorKey: "lastActive",
|
|
67
|
+
header: "Last Active",
|
|
68
|
+
size: 140,
|
|
69
|
+
cell: ({ row }: { row: TableRow<any> }) => {
|
|
70
|
+
const user = row.original;
|
|
71
|
+
return `<span class="text-xs text-muted">${user.lastActive}</span>`;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "actions",
|
|
76
|
+
actions: (row: TableRow<any>) => [
|
|
77
|
+
{
|
|
78
|
+
label: "Edit Role & Permissions",
|
|
79
|
+
icon: "i-lucide-shield-check",
|
|
80
|
+
id: `edit-role-${row.original.id}`
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
label: "View Activity Log",
|
|
84
|
+
icon: "i-lucide-activity",
|
|
85
|
+
id: `activity-${row.original.id}`
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: "separator" as const
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
label: "Remove Member",
|
|
92
|
+
icon: "i-lucide-trash-2",
|
|
93
|
+
color: "error" as const,
|
|
94
|
+
id: `remove-${row.original.id}`
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
24
98
|
];
|
|
25
99
|
---
|
|
26
100
|
|
|
27
101
|
<CMSDashboardLayout title="CMS Users" description="Manage team permissions, editor roles, and workspace access" activeSection="users">
|
|
28
102
|
<Fragment slot="toolbar-right">
|
|
29
|
-
<RLAButton variant="solid" color="
|
|
103
|
+
<RLAButton variant="solid" color="primary" size="sm" leadingIcon="i-lucide-user-plus">
|
|
30
104
|
Invite Member
|
|
31
105
|
</RLAButton>
|
|
32
106
|
</Fragment>
|
|
33
107
|
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<span>{user.name}</span>
|
|
47
|
-
<span class="text-xs text-muted font-normal">{user.email}</span>
|
|
48
|
-
</div>
|
|
49
|
-
</td>
|
|
50
|
-
<td class="p-3.5 text-sm text-toned" data-col-id="role" data-slot="td">
|
|
51
|
-
<RLABadge variant="soft" color={user.role === "Super Admin" ? "primary" : user.role === "Editor" ? "blue" : "neutral"} size="xs">
|
|
52
|
-
{user.role}
|
|
53
|
-
</RLABadge>
|
|
54
|
-
</td>
|
|
55
|
-
<td class="p-3.5" data-col-id="status" data-slot="td">
|
|
56
|
-
{user.status === "active" ? (
|
|
57
|
-
<RLABadge variant="soft" color="green" size="xs">Active</RLABadge>
|
|
58
|
-
) : (
|
|
59
|
-
<RLABadge variant="soft" color="yellow" size="xs">Invited</RLABadge>
|
|
60
|
-
)}
|
|
61
|
-
</td>
|
|
62
|
-
<td class="p-3.5 text-sm text-muted text-xs" data-col-id="lastActive" data-slot="td">
|
|
63
|
-
{user.lastActive}
|
|
64
|
-
</td>
|
|
65
|
-
<td class="p-3.5 text-right whitespace-nowrap" data-col-id="actions" data-slot="td">
|
|
66
|
-
<div class="flex items-center justify-end gap-2">
|
|
67
|
-
<RLAButton variant="ghost" color="neutral" size="xs">Change Role</RLAButton>
|
|
68
|
-
<RLAButton variant="ghost" color="error" size="xs" leadingIcon="i-lucide-trash-2" aria-label="Remove user" />
|
|
69
|
-
</div>
|
|
70
|
-
</td>
|
|
71
|
-
</tr>
|
|
72
|
-
))}
|
|
73
|
-
</RLATable>
|
|
108
|
+
<div class="flex flex-col gap-4">
|
|
109
|
+
<RLATable
|
|
110
|
+
data={users}
|
|
111
|
+
columns={userColumns}
|
|
112
|
+
striped
|
|
113
|
+
hoverable
|
|
114
|
+
searchable
|
|
115
|
+
showColumnsToggle
|
|
116
|
+
exportable
|
|
117
|
+
exportFilename="rimelight-users-export"
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
74
120
|
</CMSDashboardLayout>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import CMSDashboardLayout from "../layouts/CMSDashboardLayout.astro";
|
|
3
|
-
import
|
|
3
|
+
import RLATable from "@rimelight/ui/components/table/RLATable.astro";
|
|
4
|
+
import type { TableColumn, TableRow } from "@rimelight/ui/components/table/table.ts";
|
|
4
5
|
import { db } from "virtual:rimelight-cms/db";
|
|
5
6
|
import { pageVersions, pages } from "../../schema";
|
|
6
7
|
import { desc, eq } from "drizzle-orm";
|
|
@@ -31,44 +32,55 @@ if (db) {
|
|
|
31
32
|
console.warn("Failed to fetch versions:", err);
|
|
32
33
|
}
|
|
33
34
|
}
|
|
35
|
+
|
|
36
|
+
const columns: TableColumn<any>[] = [
|
|
37
|
+
{
|
|
38
|
+
accessorKey: "pageTitle",
|
|
39
|
+
header: "Page",
|
|
40
|
+
cell: ({ row }: { row: TableRow<any> }) => {
|
|
41
|
+
const v = row.original;
|
|
42
|
+
return `<div>
|
|
43
|
+
<div class="text-sm font-semibold text-highlighted">${v.pageTitle}</div>
|
|
44
|
+
<div class="text-xs font-mono text-muted">/${v.slug}</div>
|
|
45
|
+
</div>`;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
accessorKey: "versionNumber",
|
|
50
|
+
header: "Version",
|
|
51
|
+
size: 110,
|
|
52
|
+
cell: ({ row }: { row: TableRow<any> }) => `<span class="font-mono text-xs text-muted font-bold">v${row.getValue("versionNumber")}</span>`
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
accessorKey: "status",
|
|
56
|
+
header: "Status",
|
|
57
|
+
size: 140,
|
|
58
|
+
cell: ({ row }: { row: TableRow<any> }) => {
|
|
59
|
+
const st = row.getValue("status");
|
|
60
|
+
const color = st === "published" ? "success" : st === "pending" ? "warning" : "neutral";
|
|
61
|
+
return `<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold capitalize bg-${color}-500/10 text-${color}-500 border border-${color}-500/20">${st}</span>`;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
accessorKey: "createdAt",
|
|
66
|
+
header: "Created",
|
|
67
|
+
size: 150,
|
|
68
|
+
cell: ({ row }: { row: TableRow<any> }) => `<span class="text-xs text-muted font-mono">${new Date(row.getValue("createdAt")).toLocaleDateString(currentLocale)}</span>`
|
|
69
|
+
}
|
|
70
|
+
];
|
|
34
71
|
---
|
|
35
72
|
|
|
36
73
|
<CMSDashboardLayout title="Versions" description="Manage CMS page versions" activeSection="versions">
|
|
37
|
-
<div class="
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
{versions.map((version: any) => (
|
|
49
|
-
<tr class="hover:bg-elevated/50 transition">
|
|
50
|
-
<td class="px-6 py-4 whitespace-nowrap">
|
|
51
|
-
<div class="text-sm font-medium text-highlighted">{version.pageTitle}</div>
|
|
52
|
-
<div class="text-xs font-mono text-muted">{version.slug}</div>
|
|
53
|
-
</td>
|
|
54
|
-
<td class="px-6 py-4 whitespace-nowrap text-xs font-mono text-muted">
|
|
55
|
-
v{version.versionNumber}
|
|
56
|
-
</td>
|
|
57
|
-
<td class="px-6 py-4 whitespace-nowrap">
|
|
58
|
-
{version.status === 'published' ? (
|
|
59
|
-
<RLABadge variant="soft" color="primary" size="xs">Published</RLABadge>
|
|
60
|
-
) : version.status === 'pending' ? (
|
|
61
|
-
<RLABadge variant="soft" color="warning" size="xs">Pending Review</RLABadge>
|
|
62
|
-
) : (
|
|
63
|
-
<RLABadge variant="soft" color="neutral" size="xs">Draft</RLABadge>
|
|
64
|
-
)}
|
|
65
|
-
</td>
|
|
66
|
-
<td class="px-6 py-4 whitespace-nowrap text-xs text-muted">
|
|
67
|
-
{new Date(version.createdAt).toLocaleDateString(currentLocale)}
|
|
68
|
-
</td>
|
|
69
|
-
</tr>
|
|
70
|
-
))}
|
|
71
|
-
</tbody>
|
|
72
|
-
</table>
|
|
74
|
+
<div class="flex flex-col gap-4">
|
|
75
|
+
<RLATable
|
|
76
|
+
data={versions}
|
|
77
|
+
columns={columns}
|
|
78
|
+
striped
|
|
79
|
+
hoverable
|
|
80
|
+
searchable
|
|
81
|
+
showColumnsToggle
|
|
82
|
+
exportable
|
|
83
|
+
exportFilename="rimelight-versions-export"
|
|
84
|
+
/>
|
|
73
85
|
</div>
|
|
74
86
|
</CMSDashboardLayout>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock } from "../../core/types/blocks"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
block: BaseBlock
|
|
6
|
+
locale?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { block } = Astro.props
|
|
10
|
+
const { character = "CHARACTER", parenthetical, line = "" } = block.props || {}
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div class="cms-dialogue-block max-w-xl mx-auto my-3 font-mono text-sm text-center" data-block-id={block.id}>
|
|
14
|
+
<div class="font-bold tracking-wider uppercase text-neutral-900 dark:text-neutral-100">
|
|
15
|
+
{character}
|
|
16
|
+
</div>
|
|
17
|
+
{parenthetical && (
|
|
18
|
+
<div class="text-xs italic text-neutral-500 dark:text-neutral-400">
|
|
19
|
+
({parenthetical})
|
|
20
|
+
</div>
|
|
21
|
+
)}
|
|
22
|
+
<div class="text-left text-neutral-800 dark:text-neutral-200 mt-1 max-w-md mx-auto leading-relaxed">
|
|
23
|
+
{line}
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BaseBlock } from "../../core/types/blocks"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
block: BaseBlock
|
|
6
|
+
locale?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { block } = Astro.props
|
|
10
|
+
const { componentCode = "", framework = "astro", height = "300px", width = "100%", editable = false } = block.props || {}
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
class="cms-live-preview-block my-6 rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-neutral-900 overflow-hidden shadow-sm"
|
|
15
|
+
data-block-id={block.id}
|
|
16
|
+
>
|
|
17
|
+
<div class="flex items-center justify-between px-4 py-2 bg-neutral-950/80 border-b border-neutral-800 text-xs text-neutral-400">
|
|
18
|
+
<div class="flex items-center gap-2">
|
|
19
|
+
<span class="size-2 rounded-full bg-emerald-500 animate-pulse"></span>
|
|
20
|
+
<span class="font-mono uppercase font-semibold text-neutral-300">Preview ({framework})</span>
|
|
21
|
+
</div>
|
|
22
|
+
{editable && <span class="text-xs text-neutral-500">Interactive</span>}
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div
|
|
26
|
+
class="p-6 bg-neutral-900 flex items-center justify-center min-h-[160px] overflow-auto"
|
|
27
|
+
style={{ height, width }}
|
|
28
|
+
>
|
|
29
|
+
<div class="text-xs text-neutral-400 font-mono bg-neutral-950/60 p-4 rounded-xl border border-neutral-800 max-w-full overflow-auto">
|
|
30
|
+
<pre><code>{componentCode}</code></pre>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|