@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
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
import CMSDashboardLayout from "../layouts/CMSDashboardLayout.astro";
|
|
3
|
+
import RLAButton from "@rimelight/ui/components/button/RLAButton.astro";
|
|
4
|
+
import RLAInput from "@rimelight/ui/components/input/RLAInput.astro";
|
|
5
|
+
import RLATextarea from "@rimelight/ui/components/textarea/RLATextarea.astro";
|
|
6
|
+
import RLASelect from "@rimelight/ui/components/select/RLASelect.astro";
|
|
7
|
+
import { getRelativeLocaleUrl } from "@rimelight/i18n";
|
|
8
|
+
|
|
9
|
+
export const prerender = false;
|
|
10
|
+
|
|
11
|
+
const currentLocale = Astro.currentLocale ?? "en";
|
|
12
|
+
|
|
13
|
+
const defaultRolesList = [
|
|
14
|
+
{ name: "admin", displayName: "Administrator", description: "Full system & CMS access" },
|
|
15
|
+
{ name: "editor", displayName: "Editor", description: "Can create and publish pages" },
|
|
16
|
+
{ name: "author", displayName: "Author", description: "Can create and edit drafts" },
|
|
17
|
+
{ name: "reviewer", displayName: "Reviewer", description: "Can review & approve versions" },
|
|
18
|
+
{ name: "viewer", displayName: "Viewer", description: "Read-only access" }
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const roleOptions = defaultRolesList.map((r) => ({
|
|
22
|
+
label: r.displayName || r.name,
|
|
23
|
+
value: r.name,
|
|
24
|
+
description: r.description || ""
|
|
25
|
+
}));
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<CMSDashboardLayout title="Create Template" description="Create a new CMS template" activeSection="templates">
|
|
29
|
+
<div class="max-w-3xl mx-auto space-y-6">
|
|
30
|
+
<form id="template-form" class="bg-elevated/40 border border-default p-6 rounded-2xl shadow space-y-6">
|
|
31
|
+
<div>
|
|
32
|
+
<label class="block text-sm font-medium text-highlighted mb-2">Slug</label>
|
|
33
|
+
<RLAInput name="slug" required />
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div>
|
|
37
|
+
<label class="block text-sm font-medium text-highlighted mb-2">Title</label>
|
|
38
|
+
<RLAInput name="title" required />
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div>
|
|
42
|
+
<label class="block text-sm font-medium text-highlighted mb-2">Description</label>
|
|
43
|
+
<RLATextarea name="description" rows="3" />
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div>
|
|
47
|
+
<label class="block text-sm font-medium text-highlighted mb-2">Page Type</label>
|
|
48
|
+
<RLASelect
|
|
49
|
+
name="pageType"
|
|
50
|
+
placeholder="Select page type..."
|
|
51
|
+
items={[
|
|
52
|
+
{ label: "Blog Post", value: "blog" },
|
|
53
|
+
{ label: "Wiki Page", value: "wiki" },
|
|
54
|
+
{ label: "Documentation", value: "doc" },
|
|
55
|
+
{ label: "Announcement", value: "announcement" }
|
|
56
|
+
]}
|
|
57
|
+
required
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="border-t border-default pt-6 space-y-4">
|
|
62
|
+
<h3 class="text-md font-semibold text-highlighted">Role Permissions</h3>
|
|
63
|
+
<p class="text-xs text-muted mb-4">Define granular user role access for pages instantiated from this template.</p>
|
|
64
|
+
|
|
65
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
66
|
+
<div>
|
|
67
|
+
<label class="block text-sm font-medium text-highlighted mb-1">Who Can Create</label>
|
|
68
|
+
<RLASelect
|
|
69
|
+
name="whoCanCreate"
|
|
70
|
+
placeholder="Select creator roles..."
|
|
71
|
+
multiple={true}
|
|
72
|
+
items={roleOptions}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div>
|
|
77
|
+
<label class="block text-sm font-medium text-highlighted mb-1">Who Can Edit</label>
|
|
78
|
+
<RLASelect
|
|
79
|
+
name="whoCanEdit"
|
|
80
|
+
placeholder="Select editor roles..."
|
|
81
|
+
multiple={true}
|
|
82
|
+
items={roleOptions}
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div>
|
|
87
|
+
<label class="block text-sm font-medium text-highlighted mb-1">Who Can Review</label>
|
|
88
|
+
<RLASelect
|
|
89
|
+
name="whoCanReview"
|
|
90
|
+
placeholder="Select reviewer roles..."
|
|
91
|
+
multiple={true}
|
|
92
|
+
items={roleOptions}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div>
|
|
97
|
+
<label class="block text-sm font-medium text-highlighted mb-1">Who Can View</label>
|
|
98
|
+
<RLASelect
|
|
99
|
+
name="whoCanView"
|
|
100
|
+
placeholder="Select viewer roles..."
|
|
101
|
+
multiple={true}
|
|
102
|
+
items={roleOptions}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div class="border-t border-default pt-6">
|
|
109
|
+
<rimelight-template-editor id="template-block-editor"></rimelight-template-editor>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div class="border-t border-default pt-6 flex items-center justify-end gap-3">
|
|
113
|
+
<RLAButton href={getRelativeLocaleUrl(currentLocale, "/cms/templates")} variant="outline" color="neutral" size="sm">
|
|
114
|
+
Cancel
|
|
115
|
+
</RLAButton>
|
|
116
|
+
<RLAButton variant="solid" color="primary" size="sm" type="submit">
|
|
117
|
+
Create Template
|
|
118
|
+
</RLAButton>
|
|
119
|
+
</div>
|
|
120
|
+
</form>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<script is:inline define:vars={{ currentLocale }}>
|
|
124
|
+
import('@rimelight/cms/client/loader').then(({ defineCustomElements }) => {
|
|
125
|
+
return defineCustomElements();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
function parseRoles(rawVal) {
|
|
129
|
+
if (!rawVal) return [];
|
|
130
|
+
try {
|
|
131
|
+
const parsed = JSON.parse(rawVal);
|
|
132
|
+
return Array.isArray(parsed) ? parsed : [rawVal];
|
|
133
|
+
} catch {
|
|
134
|
+
return rawVal.split(',').map(r => r.trim()).filter(Boolean);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
document.getElementById('template-form')?.addEventListener('submit', async (e) => {
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
|
|
141
|
+
const formData = new FormData(e.target);
|
|
142
|
+
const whoCanCreate = parseRoles(formData.get('whoCanCreate'));
|
|
143
|
+
const whoCanEdit = parseRoles(formData.get('whoCanEdit'));
|
|
144
|
+
const whoCanReview = parseRoles(formData.get('whoCanReview'));
|
|
145
|
+
const whoCanView = parseRoles(formData.get('whoCanView'));
|
|
146
|
+
|
|
147
|
+
const templateEditor = document.getElementById('template-block-editor');
|
|
148
|
+
const initialBlocks = templateEditor && typeof templateEditor.getBlocks === 'function'
|
|
149
|
+
? templateEditor.getBlocks()
|
|
150
|
+
: [];
|
|
151
|
+
|
|
152
|
+
const rolePermissions = {
|
|
153
|
+
whoCanCreate,
|
|
154
|
+
whoCanEdit,
|
|
155
|
+
whoCanReview,
|
|
156
|
+
whoCanView
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const data = {
|
|
160
|
+
slug: formData.get('slug'),
|
|
161
|
+
title: formData.get('title'),
|
|
162
|
+
description: formData.get('description'),
|
|
163
|
+
pageType: formData.get('pageType'),
|
|
164
|
+
allowedRoles: whoCanCreate,
|
|
165
|
+
rolePermissions,
|
|
166
|
+
initialBlocks,
|
|
167
|
+
approvalRules: {
|
|
168
|
+
allowSelfApproval: true,
|
|
169
|
+
minApprovals: 1,
|
|
170
|
+
requiredRolesForApproval: whoCanReview
|
|
171
|
+
},
|
|
172
|
+
defaultProperties: {}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const response = await fetch('/api/cms/templates', {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers: { 'Content-Type': 'application/json' },
|
|
179
|
+
body: JSON.stringify(data)
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (response.ok) {
|
|
183
|
+
window.location.href = `/${currentLocale}/cms/templates`;
|
|
184
|
+
} else {
|
|
185
|
+
const errorData = (await response.json().catch(() => ({})));
|
|
186
|
+
alert(`Failed to create template: ${errorData.error || 'Unknown error'}`);
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
alert('Error creating template: Network error');
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
</script>
|
|
193
|
+
</CMSDashboardLayout>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
import CMSDashboardLayout from "../layouts/CMSDashboardLayout.astro";
|
|
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
|
+
import RLATable from "@rimelight/ui/components/table/RLATable.astro";
|
|
7
|
+
import type { TableColumn } from "@rimelight/ui/components/table/table.ts";
|
|
8
|
+
|
|
9
|
+
export const prerender = false;
|
|
10
|
+
|
|
11
|
+
const users = [
|
|
12
|
+
{ id: "usr_1", name: "Idan T.", email: "idan@rimelight.com", role: "Super Admin", status: "active", lastActive: "Just now" },
|
|
13
|
+
{ id: "usr_2", name: "Sarah Connor", email: "sarah@rimelight.com", role: "Editor", status: "active", lastActive: "2 hours ago" },
|
|
14
|
+
{ id: "usr_3", name: "Alex Rivers", email: "alex@rimelight.com", role: "Author", status: "active", lastActive: "Yesterday" },
|
|
15
|
+
{ id: "usr_4", name: "Elena Vance", email: "elena@rimelight.com", role: "Reviewer", status: "invited", lastActive: "Never" },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const columns: TableColumn[] = [
|
|
19
|
+
{ id: "user", accessorKey: "name", header: "User", enableSorting: true },
|
|
20
|
+
{ id: "role", accessorKey: "role", header: "Role", enableSorting: true },
|
|
21
|
+
{ id: "status", accessorKey: "status", header: "Status", enableSorting: true },
|
|
22
|
+
{ id: "lastActive", accessorKey: "lastActive", header: "Last Active", enableSorting: true },
|
|
23
|
+
{ id: "actions", header: "Actions", enableSorting: false, meta: { class: { th: "text-right", td: "text-right" } } }
|
|
24
|
+
];
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<CMSDashboardLayout title="CMS Users" description="Manage team permissions, editor roles, and workspace access" activeSection="users">
|
|
28
|
+
<Fragment slot="toolbar-right">
|
|
29
|
+
<RLAButton variant="solid" color="success" leadingIcon="i-lucide-user-plus">
|
|
30
|
+
Invite Member
|
|
31
|
+
</RLAButton>
|
|
32
|
+
</Fragment>
|
|
33
|
+
|
|
34
|
+
<RLATable
|
|
35
|
+
columns={columns}
|
|
36
|
+
data={users}
|
|
37
|
+
searchable
|
|
38
|
+
searchPlaceholder="Search users..."
|
|
39
|
+
class="w-full"
|
|
40
|
+
>
|
|
41
|
+
{users.map((user) => (
|
|
42
|
+
<tr class="hover:bg-muted/20 transition-colors" data-slot="tr" data-row-id={user.id}>
|
|
43
|
+
<td class="p-3.5 text-sm font-medium text-highlighted flex items-center gap-3" data-col-id="user" data-slot="td">
|
|
44
|
+
<RLAAvatar name={user.name} size="sm" />
|
|
45
|
+
<div class="flex flex-col">
|
|
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>
|
|
74
|
+
</CMSDashboardLayout>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
import CMSDashboardLayout from "../layouts/CMSDashboardLayout.astro";
|
|
3
|
+
import RLABadge from "@rimelight/ui/components/badge/RLABadge.astro";
|
|
4
|
+
import { db } from "virtual:rimelight-cms/db";
|
|
5
|
+
import { pageVersions, pages } from "../../schema";
|
|
6
|
+
import { desc, eq } from "drizzle-orm";
|
|
7
|
+
|
|
8
|
+
export const prerender = false;
|
|
9
|
+
|
|
10
|
+
const currentLocale = Astro.currentLocale ?? "en";
|
|
11
|
+
|
|
12
|
+
let versions: any[] = [];
|
|
13
|
+
if (db) {
|
|
14
|
+
try {
|
|
15
|
+
const versionsList = await db
|
|
16
|
+
.select()
|
|
17
|
+
.from(pageVersions)
|
|
18
|
+
.orderBy(desc(pageVersions.createdAt))
|
|
19
|
+
.limit(50);
|
|
20
|
+
|
|
21
|
+
versions = await Promise.all(
|
|
22
|
+
versionsList.map(async (version: any) => {
|
|
23
|
+
const page = await db.select({ title: pages.title, slug: pages.slug }).from(pages).where(eq(pages.id, version.pageId)).limit(1);
|
|
24
|
+
return Object.assign({}, version, {
|
|
25
|
+
pageTitle: typeof page[0]?.title === "object" && page[0]?.title !== null ? (page[0].title as any).en || "" : page[0]?.title || "Unknown Page",
|
|
26
|
+
slug: page[0]?.slug || ""
|
|
27
|
+
});
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.warn("Failed to fetch versions:", err);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
<CMSDashboardLayout title="Versions" description="Manage CMS page versions" activeSection="versions">
|
|
37
|
+
<div class="bg-elevated/40 border border-default rounded-xl overflow-hidden">
|
|
38
|
+
<table class="w-full text-left text-sm">
|
|
39
|
+
<thead class="bg-elevated/70 border-b border-default text-xs font-semibold text-muted uppercase tracking-wider">
|
|
40
|
+
<tr>
|
|
41
|
+
<th class="px-6 py-3">Page</th>
|
|
42
|
+
<th class="px-6 py-3">Version</th>
|
|
43
|
+
<th class="px-6 py-3">Status</th>
|
|
44
|
+
<th class="px-6 py-3">Created</th>
|
|
45
|
+
</tr>
|
|
46
|
+
</thead>
|
|
47
|
+
<tbody class="divide-y divide-default/50">
|
|
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>
|
|
73
|
+
</div>
|
|
74
|
+
</CMSDashboardLayout>
|
|
@@ -1,111 +1,59 @@
|
|
|
1
1
|
---
|
|
2
|
-
import type { BaseBlock } from "
|
|
3
|
-
import
|
|
4
|
-
import CalloutBlock from "./blocks/CalloutBlock.astro"
|
|
5
|
-
import ImageBlock from "./blocks/ImageBlock.astro"
|
|
6
|
-
import CodeBlock from "./blocks/CodeBlock.astro"
|
|
7
|
-
import ScriptBlock from "./blocks/ScriptBlock.astro"
|
|
8
|
-
import SceneBlock from "./blocks/SceneBlock.astro"
|
|
9
|
-
import DialogueBlock from "./blocks/DialogueBlock.astro"
|
|
10
|
-
import SectionBlock from "./blocks/SectionBlock.astro"
|
|
11
|
-
import TableBlock from "./blocks/TableBlock.astro"
|
|
12
|
-
import TabsBlock from "./blocks/TabsBlock.astro"
|
|
13
|
-
import TabItemBlock from "./blocks/TabItemBlock.astro"
|
|
14
|
-
import StepsBlock from "./blocks/StepsBlock.astro"
|
|
15
|
-
import StepItemBlock from "./blocks/StepItemBlock.astro"
|
|
16
|
-
import CardsBlock from "./blocks/CardsBlock.astro"
|
|
17
|
-
import CardBlock from "./blocks/CardBlock.astro"
|
|
18
|
-
import FileTreeBlock from "./blocks/FileTreeBlock.astro"
|
|
19
|
-
import UnorderedListBlock from "./blocks/UnorderedListBlock.astro"
|
|
20
|
-
import OrderedListBlock from "./blocks/OrderedListBlock.astro"
|
|
2
|
+
import type { BaseBlock } from "../core/types/blocks"
|
|
3
|
+
import { defaultBlockComponents } from "./blocks/index.ts"
|
|
21
4
|
|
|
22
5
|
interface Props {
|
|
23
6
|
block: BaseBlock
|
|
24
7
|
locale?: string
|
|
25
8
|
sectionDepth?: number
|
|
26
9
|
index?: number
|
|
10
|
+
components?: Record<string, any>
|
|
27
11
|
}
|
|
28
12
|
|
|
29
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
block,
|
|
15
|
+
locale = "en",
|
|
16
|
+
sectionDepth = 0,
|
|
17
|
+
index = 0,
|
|
18
|
+
components = {}
|
|
19
|
+
} = Astro.props
|
|
20
|
+
|
|
21
|
+
const Component = components[block.type] ?? defaultBlockComponents[block.type]
|
|
22
|
+
const rawChildren = block.children || block.props?.children || []
|
|
23
|
+
const children = (Array.isArray(rawChildren) ? rawChildren : []) as BaseBlock[]
|
|
24
|
+
const nextDepth = block.type === "SectionBlock" ? Math.min(6, (sectionDepth || 2) + 1) : sectionDepth
|
|
30
25
|
---
|
|
31
26
|
|
|
32
27
|
{
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
62
|
-
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
63
|
-
))}
|
|
64
|
-
</StepsBlock>
|
|
65
|
-
) : block.type === "StepItemBlock" ? (
|
|
66
|
-
<StepItemBlock block={block} locale={locale} index={index}>
|
|
67
|
-
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
68
|
-
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
28
|
+
Component ? (
|
|
29
|
+
<Component
|
|
30
|
+
block={block}
|
|
31
|
+
locale={locale}
|
|
32
|
+
sectionDepth={sectionDepth}
|
|
33
|
+
index={index}
|
|
34
|
+
components={components}
|
|
35
|
+
>
|
|
36
|
+
{children.map((child: BaseBlock, idx: number) => (
|
|
37
|
+
<Astro.self
|
|
38
|
+
block={child}
|
|
39
|
+
locale={locale}
|
|
40
|
+
sectionDepth={nextDepth}
|
|
41
|
+
index={idx}
|
|
42
|
+
components={components}
|
|
43
|
+
/>
|
|
44
|
+
))}
|
|
45
|
+
</Component>
|
|
46
|
+
) : children.length > 0 ? (
|
|
47
|
+
<div>
|
|
48
|
+
{children.map((child: BaseBlock, idx: number) => (
|
|
49
|
+
<Astro.self
|
|
50
|
+
block={child}
|
|
51
|
+
locale={locale}
|
|
52
|
+
sectionDepth={sectionDepth}
|
|
53
|
+
index={idx}
|
|
54
|
+
components={components}
|
|
55
|
+
/>
|
|
69
56
|
))}
|
|
70
|
-
</StepItemBlock>
|
|
71
|
-
) : block.type === "CardsBlock" ? (
|
|
72
|
-
<CardsBlock block={block} locale={locale}>
|
|
73
|
-
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
74
|
-
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
75
|
-
))}
|
|
76
|
-
</CardsBlock>
|
|
77
|
-
) : block.type === "CardBlock" ? (
|
|
78
|
-
<CardBlock block={block} locale={locale} />
|
|
79
|
-
) : block.type === "FileTreeBlock" ? (
|
|
80
|
-
<FileTreeBlock block={block} locale={locale} />
|
|
81
|
-
) : block.type === "UnorderedListBlock" ? (
|
|
82
|
-
<UnorderedListBlock block={block} locale={locale} />
|
|
83
|
-
) : block.type === "OrderedListBlock" ? (
|
|
84
|
-
<OrderedListBlock block={block} locale={locale} />
|
|
85
|
-
) : block.type === "ScriptBlock" ? (
|
|
86
|
-
<ScriptBlock block={block} locale={locale}>
|
|
87
|
-
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
88
|
-
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
89
|
-
))}
|
|
90
|
-
</ScriptBlock>
|
|
91
|
-
) : block.type === "SceneBlock" ? (
|
|
92
|
-
<SceneBlock block={block} locale={locale}>
|
|
93
|
-
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
94
|
-
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth} index={idx} />
|
|
95
|
-
))}
|
|
96
|
-
</SceneBlock>
|
|
97
|
-
) : block.type === "DialogueBlock" ? (
|
|
98
|
-
<DialogueBlock block={block} locale={locale} />
|
|
99
|
-
) : block.type === "SectionBlock" ? (
|
|
100
|
-
<SectionBlock block={block} locale={locale} sectionDepth={sectionDepth}>
|
|
101
|
-
{(block.children || block.props?.children)?.map((child: BaseBlock, idx: number) => (
|
|
102
|
-
<Astro.self block={child} locale={locale} sectionDepth={sectionDepth + 1} index={idx} />
|
|
103
|
-
))}
|
|
104
|
-
</SectionBlock>
|
|
105
|
-
) : (
|
|
106
|
-
<div class="cms-block-fallback p-4 border border-dashed border-gray-700 rounded my-2 text-sm text-gray-400">
|
|
107
|
-
Unknown Block Type: {block.type}
|
|
108
57
|
</div>
|
|
109
|
-
)
|
|
58
|
+
) : null
|
|
110
59
|
}
|
|
111
|
-
|
|
@@ -9,15 +9,22 @@ interface Props {
|
|
|
9
9
|
session?: UserSessionContext
|
|
10
10
|
locale?: string
|
|
11
11
|
class?: string
|
|
12
|
+
components?: Record<string, any>
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
const {
|
|
15
|
+
const {
|
|
16
|
+
blocks,
|
|
17
|
+
session,
|
|
18
|
+
locale = "en",
|
|
19
|
+
class: className = "",
|
|
20
|
+
components = {}
|
|
21
|
+
} = Astro.props
|
|
15
22
|
|
|
16
23
|
const sanitizedBlocks = filterBlocksForReader(blocks, session)
|
|
17
24
|
---
|
|
18
25
|
|
|
19
26
|
<article class={`rimelight-cms-page ${className}`}>
|
|
20
27
|
{sanitizedBlocks.map((block) => (
|
|
21
|
-
<BlockRenderer block={block} locale={locale} />
|
|
28
|
+
<BlockRenderer block={block} locale={locale} components={components} />
|
|
22
29
|
))}
|
|
23
30
|
</article>
|
|
@@ -20,43 +20,43 @@ const variantConfigs: Record<
|
|
|
20
20
|
info: {
|
|
21
21
|
icon: "i-lucide-info",
|
|
22
22
|
classes:
|
|
23
|
-
"bg-
|
|
23
|
+
"bg-info-50/50 border-info-200 text-info-800",
|
|
24
24
|
label: "Info"
|
|
25
25
|
},
|
|
26
26
|
success: {
|
|
27
27
|
icon: "i-lucide-check-circle",
|
|
28
28
|
classes:
|
|
29
|
-
"bg-
|
|
29
|
+
"bg-success-50/50 border-success-200 text-success-800",
|
|
30
30
|
label: "Success"
|
|
31
31
|
},
|
|
32
32
|
warning: {
|
|
33
33
|
icon: "i-lucide-alert-triangle",
|
|
34
34
|
classes:
|
|
35
|
-
"bg-
|
|
35
|
+
"bg-warning-50/50 border-warning-200 text-warning-800",
|
|
36
36
|
label: "Warning"
|
|
37
37
|
},
|
|
38
38
|
error: {
|
|
39
39
|
icon: "i-lucide-alert-octagon",
|
|
40
40
|
classes:
|
|
41
|
-
"bg-
|
|
41
|
+
"bg-error-50/50 border-error-200 text-error-800",
|
|
42
42
|
label: "Error"
|
|
43
43
|
},
|
|
44
44
|
commentary: {
|
|
45
45
|
icon: "i-lucide-message-square",
|
|
46
46
|
classes:
|
|
47
|
-
"bg-
|
|
47
|
+
"bg-ideation-50/50 border-ideation-200 text-ideation-800",
|
|
48
48
|
label: "Commentary"
|
|
49
49
|
},
|
|
50
50
|
ideation: {
|
|
51
51
|
icon: "i-lucide-lightbulb",
|
|
52
52
|
classes:
|
|
53
|
-
"bg-
|
|
53
|
+
"bg-warning-50/50 border-warning-200 text-warning-800",
|
|
54
54
|
label: "Ideation"
|
|
55
55
|
},
|
|
56
56
|
source: {
|
|
57
57
|
icon: "i-lucide-code",
|
|
58
58
|
classes:
|
|
59
|
-
"bg-
|
|
59
|
+
"bg-info-50/50 border-info-200 text-info-800",
|
|
60
60
|
label: "Source"
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -13,30 +13,30 @@ const Tag = href ? "a" : "div"
|
|
|
13
13
|
|
|
14
14
|
<Tag
|
|
15
15
|
href={href}
|
|
16
|
-
class="cms-card group relative block p-5 rounded-xl border border-neutral-200
|
|
16
|
+
class="cms-card group relative block p-5 rounded-xl border border-neutral-200 bg-elevated hover:border-primary-500/50 hover:shadow-md transition-all text-decoration-none no-underline"
|
|
17
17
|
data-block-id={block.id}
|
|
18
18
|
>
|
|
19
19
|
<div class="flex items-start justify-between gap-2">
|
|
20
20
|
<div class="flex items-center gap-3">
|
|
21
21
|
{icon && (
|
|
22
|
-
<div class="flex size-9 shrink-0 items-center justify-center rounded-lg bg-neutral-100
|
|
22
|
+
<div class="flex size-9 shrink-0 items-center justify-center rounded-lg bg-neutral-100 text-neutral-800 group-hover:bg-primary-50 group-hover:text-primary-600 transition-colors">
|
|
23
23
|
<span class={`${icon} size-5`} aria-hidden="true" />
|
|
24
24
|
</div>
|
|
25
25
|
)}
|
|
26
26
|
{title && (
|
|
27
|
-
<h4 class="text-base font-semibold text-neutral-900
|
|
27
|
+
<h4 class="text-base font-semibold text-neutral-900 m-0 group-hover:text-primary-600 transition-colors">
|
|
28
28
|
{title}
|
|
29
29
|
</h4>
|
|
30
30
|
)}
|
|
31
31
|
</div>
|
|
32
32
|
{badge && (
|
|
33
|
-
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-neutral-100
|
|
33
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-neutral-100 text-neutral-700">
|
|
34
34
|
{badge}
|
|
35
35
|
</span>
|
|
36
36
|
)}
|
|
37
37
|
</div>
|
|
38
38
|
{description && (
|
|
39
|
-
<p class="mt-3 text-sm text-neutral-600
|
|
39
|
+
<p class="mt-3 text-sm text-neutral-600 m-0">
|
|
40
40
|
{description}
|
|
41
41
|
</p>
|
|
42
42
|
)}
|
|
@@ -10,7 +10,7 @@ const { block } = Astro.props
|
|
|
10
10
|
const { code = "", language = "text" } = block.props
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
<div class="cms-code my-4 rounded-lg bg-
|
|
14
|
-
<div class="text-xs text-
|
|
13
|
+
<div class="cms-code my-4 rounded-lg bg-neutral-950 p-4 font-mono text-sm border border-neutral-800 overflow-x-auto" data-block-id={block.id}>
|
|
14
|
+
<div class="text-xs text-neutral-500 uppercase mb-2 select-none">{language}</div>
|
|
15
15
|
<pre class="m-0"><code>{code}</code></pre>
|
|
16
16
|
</div>
|
|
@@ -10,31 +10,31 @@ const { block } = Astro.props
|
|
|
10
10
|
const { tree = [] } = block.props || {}
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
<div class="cms-file-tree my-6 rounded-xl border border-neutral-200
|
|
13
|
+
<div class="cms-file-tree my-6 rounded-xl border border-neutral-200 bg-neutral-50/50 p-4 font-mono text-sm" data-block-id={block.id}>
|
|
14
14
|
<div class="cms-file-tree-root space-y-1">
|
|
15
15
|
{tree.map(function renderNode(node: FileTreeNode) {
|
|
16
16
|
return (
|
|
17
17
|
<div class="cms-tree-item">
|
|
18
|
-
<div class="flex items-center gap-2 py-0.5 text-neutral-800
|
|
18
|
+
<div class="flex items-center gap-2 py-0.5 text-neutral-800">
|
|
19
19
|
<span
|
|
20
20
|
class={`${
|
|
21
21
|
node.isDir
|
|
22
|
-
? "i-lucide-folder text-
|
|
23
|
-
: "i-lucide-file-text text-neutral-400
|
|
22
|
+
? "i-lucide-folder text-warning-500"
|
|
23
|
+
: "i-lucide-file-text text-neutral-400"
|
|
24
24
|
} size-4 shrink-0`}
|
|
25
25
|
aria-hidden="true"
|
|
26
26
|
/>
|
|
27
|
-
<span class={`font-medium ${node.isDir ? "text-neutral-900
|
|
27
|
+
<span class={`font-medium ${node.isDir ? "text-neutral-900" : ""}`}>
|
|
28
28
|
{node.name}
|
|
29
29
|
</span>
|
|
30
30
|
{node.comment && (
|
|
31
|
-
<span class="text-xs text-neutral-500
|
|
31
|
+
<span class="text-xs text-neutral-500 italic font-sans ml-2">
|
|
32
32
|
— {node.comment}
|
|
33
33
|
</span>
|
|
34
34
|
)}
|
|
35
35
|
</div>
|
|
36
36
|
{node.children && node.children.length > 0 && (
|
|
37
|
-
<div class="pl-4 ml-2 border-l border-neutral-200
|
|
37
|
+
<div class="pl-4 ml-2 border-l border-neutral-200 space-y-1">
|
|
38
38
|
{node.children.map(renderNode)}
|
|
39
39
|
</div>
|
|
40
40
|
)}
|