@rimelight/cms 0.0.2 → 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.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +16 -14
  3. package/src/admin/api/media-file.ts +39 -0
  4. package/src/admin/api/media.ts +199 -0
  5. package/src/admin/layouts/CMSDashboardLayout.astro +178 -0
  6. package/src/admin/layouts/DefaultParentLayout.astro +50 -0
  7. package/src/admin/pages/assets.astro +424 -0
  8. package/src/admin/pages/byline-schema.astro +62 -0
  9. package/src/admin/pages/bylines.astro +16 -0
  10. package/src/admin/pages/categories.astro +21 -0
  11. package/src/admin/pages/content-types.astro +55 -0
  12. package/src/admin/pages/dashboard.astro +61 -0
  13. package/src/admin/pages/pages-edit.astro +595 -0
  14. package/src/admin/pages/pages-index.astro +146 -0
  15. package/src/admin/pages/pages-new.astro +130 -0
  16. package/src/admin/pages/pages-preview.astro +111 -0
  17. package/src/admin/pages/pages-review.astro +236 -0
  18. package/src/admin/pages/settings.astro +203 -0
  19. package/src/admin/pages/tags.astro +21 -0
  20. package/src/admin/pages/templates-edit.astro +234 -0
  21. package/src/admin/pages/templates-index.astro +95 -0
  22. package/src/admin/pages/templates-new.astro +193 -0
  23. package/src/admin/pages/users.astro +74 -0
  24. package/src/admin/pages/versions.astro +74 -0
  25. package/src/astro/BlockRenderer.astro +44 -102
  26. package/src/astro/PageRenderer.astro +9 -2
  27. package/src/astro/blocks/CalloutBlock.astro +7 -7
  28. package/src/astro/blocks/CardBlock.astro +5 -5
  29. package/src/astro/blocks/CodeBlock.astro +2 -2
  30. package/src/astro/blocks/FileTreeBlock.astro +7 -7
  31. package/src/astro/blocks/ImageBlock.astro +2 -2
  32. package/src/astro/blocks/OrderedListBlock.astro +2 -2
  33. package/src/astro/blocks/ParagraphBlock.astro +3 -3
  34. package/src/astro/blocks/SectionBlock.astro +6 -6
  35. package/src/astro/blocks/StepItemBlock.astro +3 -3
  36. package/src/astro/blocks/StepsBlock.astro +1 -1
  37. package/src/astro/blocks/TableBlock.astro +6 -6
  38. package/src/astro/blocks/TabsBlock.astro +9 -9
  39. package/src/astro/blocks/UnorderedListBlock.astro +2 -2
  40. package/src/astro/blocks/index.ts +35 -0
  41. package/src/client/components/RimelightBlock.tsx +19 -19
  42. package/src/client/components/RimelightBlockPicker.tsx +9 -13
  43. package/src/client/components/RimelightBylinesManager.tsx +438 -0
  44. package/src/client/components/RimelightEditor.tsx +3 -3
  45. package/src/client/components/RimelightPreview.tsx +37 -45
  46. package/src/client/components/RimelightPropertiesPanel.tsx +368 -26
  47. package/src/client/components/RimelightTaxonomyManager.tsx +369 -0
  48. package/src/client/components/RimelightTaxonomyMasterDetail.tsx +412 -0
  49. package/src/client/components/RimelightTemplateEditor.tsx +6 -8
  50. package/src/client/components/editors/CalloutEditor.tsx +2 -4
  51. package/src/client/components/editors/CardsEditor.tsx +2 -4
  52. package/src/client/components/editors/CodeEditor.tsx +1 -3
  53. package/src/client/components/editors/FileTreeEditor.tsx +2 -4
  54. package/src/client/components/editors/ImageEditor.tsx +3 -3
  55. package/src/client/components/editors/ParagraphEditor.tsx +2 -2
  56. package/src/client/components/editors/SceneEditor.tsx +6 -12
  57. package/src/client/components/editors/ScriptEditor.tsx +8 -16
  58. package/src/client/components/editors/SectionEditor.tsx +4 -4
  59. package/src/client/components/editors/StepItemEditor.tsx +2 -2
  60. package/src/client/components/editors/StepsEditor.tsx +2 -4
  61. package/src/client/components/editors/TabItemEditor.tsx +3 -3
  62. package/src/client/components/editors/TableEditor.tsx +5 -5
  63. package/src/client/components/editors/TabsEditor.tsx +2 -4
  64. package/src/client/components/index.ts +3 -0
  65. package/src/client/loader.ts +3 -0
  66. package/src/core/diff.ts +145 -0
  67. package/src/core/excerpt.ts +159 -0
  68. package/src/core/preview.ts +87 -0
  69. package/src/core/registry.ts +0 -86
  70. package/src/core/types/blocks.ts +6 -1
  71. package/src/core/types/bylines.ts +14 -0
  72. package/src/core/types/media.ts +13 -0
  73. package/src/core/types/taxonomies.ts +20 -0
  74. package/src/cron/scheduled.ts +57 -0
  75. package/src/docs/agent.ts +116 -0
  76. package/src/docs/config.ts +25 -0
  77. package/src/docs/index.ts +5 -0
  78. package/src/docs/routing.ts +104 -0
  79. package/src/docs/sidebar.ts +251 -0
  80. package/src/docs/toc.ts +47 -0
  81. package/src/env.d.ts +13 -0
  82. package/src/index.ts +40 -8
  83. package/src/integration.ts +196 -0
  84. package/src/loader/live.ts +254 -0
  85. package/src/markdown/index.ts +0 -1
  86. package/src/mcp/index.ts +228 -0
  87. package/src/schema/bylines.ts +23 -0
  88. package/src/schema/index.ts +3 -4
  89. package/src/schema/page_versions.ts +2 -0
  90. package/src/schema/pages.ts +2 -0
  91. package/src/schema/site_settings.ts +41 -0
  92. package/src/schema/taxonomies.ts +49 -0
  93. package/src/services/bylines.ts +74 -0
  94. package/src/services/search-indexer.ts +61 -0
  95. package/src/services/site-settings.ts +108 -0
  96. package/src/services/taxonomies.ts +276 -0
  97. package/src/storage/index.ts +253 -0
  98. package/src/astro/blocks/ApiDocumentationBlock.astro +0 -350
  99. package/src/astro/blocks/DialogueBlock.astro +0 -23
  100. package/src/astro/blocks/LivePreviewBlock.astro +0 -27
  101. package/src/astro/blocks/SceneBlock.astro +0 -65
  102. package/src/astro/blocks/ScriptBlock.astro +0 -59
  103. package/src/core/template-sync.ts +0 -75
  104. package/src/markdown/parser.ts +0 -347
  105. package/src/migration-tools.ts +0 -334
  106. package/src/migration.ts +0 -155
  107. package/src/schema/component_dependencies.ts +0 -21
  108. package/src/schema/component_metadata.ts +0 -31
  109. package/src/schema/component_metadata_cache.ts +0 -15
  110. package/src/schema/component_versions.ts +0 -19
  111. package/src/search/sync.ts +0 -32
  112. package/src/services/component-metadata.ts +0 -134
@@ -0,0 +1,412 @@
1
+ import { createSignal, onMount, For, Show, createMemo, type Component } from "solid-js"
2
+ import RLSInput from "@rimelight/ui/components/input/RLSInput.tsx"
3
+ import RLSButton from "@rimelight/ui/components/button/RLSButton.tsx"
4
+ import RLSBadge from "@rimelight/ui/components/badge/RLSBadge.tsx"
5
+ import RLSIcon from "@rimelight/ui/components/icon/RLSIcon.tsx"
6
+
7
+ export interface TaxonomyRecord {
8
+ id: string
9
+ taxonomy: string
10
+ name: string
11
+ slug: string
12
+ description?: string
13
+ parentId?: string | null
14
+ postCount?: number
15
+ }
16
+
17
+ export interface RimelightTaxonomyMasterDetailProps {
18
+ taxonomy: string
19
+ title?: string
20
+ description?: string
21
+ }
22
+
23
+ export const RimelightTaxonomyMasterDetail: Component<RimelightTaxonomyMasterDetailProps> = (
24
+ props
25
+ ) => {
26
+ const taxType = () => props.taxonomy || "category"
27
+ const isCategory = () => taxType() === "category"
28
+ const defaultTitle = () => (isCategory() ? "Categories" : "Tags")
29
+ const defaultDesc = () =>
30
+ isCategory()
31
+ ? "Organize your content into hierarchical topics and sections."
32
+ : "Label articles and posts with keywords for easy filtering."
33
+
34
+ const [terms, setTerms] = createSignal<TaxonomyRecord[]>([])
35
+ const [selectedId, setSelectedId] = createSignal<string | null>(null)
36
+ const [searchQuery, setSearchQuery] = createSignal("")
37
+ const [isLoading, setIsLoading] = createSignal(false)
38
+ const [isSaving, setIsSaving] = createSignal(false)
39
+ const [error, setError] = createSignal<string | null>(null)
40
+
41
+ // Form State
42
+ const [form, setForm] = createSignal<{
43
+ name: string
44
+ slug: string
45
+ parentId: string
46
+ description: string
47
+ }>({
48
+ name: "",
49
+ slug: "",
50
+ parentId: "",
51
+ description: ""
52
+ })
53
+
54
+ const loadTerms = async () => {
55
+ setIsLoading(true)
56
+ setError(null)
57
+ try {
58
+ const res = await fetch(`/api/cms/taxonomies/${taxType()}/terms?includeCounts=true`)
59
+ if (!res.ok) throw new Error(`Failed to load ${taxType()} terms`)
60
+ const data = await res.json()
61
+ const list: TaxonomyRecord[] = Array.isArray(data) ? data : []
62
+ setTerms(list)
63
+
64
+ if (list.length > 0 && !selectedId()) {
65
+ selectTerm(list[0]!)
66
+ } else if (list.length === 0) {
67
+ startNewTerm()
68
+ }
69
+ } catch (err: any) {
70
+ setError(err.message || "Failed to load terms")
71
+ } finally {
72
+ setIsLoading(false)
73
+ }
74
+ }
75
+
76
+ onMount(() => {
77
+ void loadTerms()
78
+ })
79
+
80
+ const filteredTerms = createMemo(() => {
81
+ const q = searchQuery().toLowerCase().trim()
82
+ if (!q) return terms()
83
+ return terms().filter(
84
+ (t) =>
85
+ t.name.toLowerCase().includes(q) ||
86
+ t.slug.toLowerCase().includes(q) ||
87
+ (t.description && t.description.toLowerCase().includes(q))
88
+ )
89
+ })
90
+
91
+ const selectTerm = (term: TaxonomyRecord) => {
92
+ setSelectedId(term.id)
93
+ setForm({
94
+ name: term.name || "",
95
+ slug: term.slug || "",
96
+ parentId: term.parentId || "",
97
+ description: term.description || ""
98
+ })
99
+ }
100
+
101
+ const startNewTerm = () => {
102
+ setSelectedId(null)
103
+ setForm({
104
+ name: "",
105
+ slug: "",
106
+ parentId: "",
107
+ description: ""
108
+ })
109
+ }
110
+
111
+ const handleNameChange = (name: string) => {
112
+ const isNew = !selectedId()
113
+ const autoSlug = isNew
114
+ ? name
115
+ .toLowerCase()
116
+ .replace(/[^a-z0-9]+/g, "-")
117
+ .replace(/(^-|-$)/g, "")
118
+ : form().slug
119
+
120
+ setForm((prev) => ({
121
+ ...prev,
122
+ name,
123
+ slug: isNew ? autoSlug : prev.slug
124
+ }))
125
+ }
126
+
127
+ const handleSave = async (e?: Event) => {
128
+ e?.preventDefault()
129
+ const f = form()
130
+ if (!f.name.trim()) {
131
+ alert("Name is required")
132
+ return
133
+ }
134
+
135
+ const slug =
136
+ f.slug.trim() ||
137
+ f.name
138
+ .toLowerCase()
139
+ .replace(/[^a-z0-9]+/g, "-")
140
+ .replace(/(^-|-$)/g, "")
141
+
142
+ const payload = {
143
+ name: f.name.trim(),
144
+ slug,
145
+ parentId: isCategory() && f.parentId ? f.parentId : null,
146
+ description: f.description.trim()
147
+ }
148
+
149
+ setIsSaving(true)
150
+ try {
151
+ if (selectedId()) {
152
+ const res = await fetch(`/api/cms/taxonomies/${taxType()}/terms/${selectedId()}`, {
153
+ method: "PUT",
154
+ headers: { "Content-Type": "application/json" },
155
+ body: JSON.stringify(payload)
156
+ })
157
+ if (!res.ok) throw new Error(`Failed to update ${taxType()}`)
158
+ const updated = await res.json()
159
+ setTerms((prev) => prev.map((t) => (t.id === updated.id ? { ...t, ...updated } : t)))
160
+ } else {
161
+ const res = await fetch(`/api/cms/taxonomies/${taxType()}/terms`, {
162
+ method: "POST",
163
+ headers: { "Content-Type": "application/json" },
164
+ body: JSON.stringify(payload)
165
+ })
166
+ if (!res.ok) throw new Error(`Failed to create ${taxType()}`)
167
+ const created = await res.json()
168
+ setTerms((prev) => [created, ...prev])
169
+ setSelectedId(created.id)
170
+ }
171
+ } catch (err: any) {
172
+ alert(err.message || `Failed to save ${taxType()}`)
173
+ } finally {
174
+ setIsSaving(false)
175
+ }
176
+ }
177
+
178
+ const handleDelete = async () => {
179
+ const id = selectedId()
180
+ if (!id) return
181
+ const current = terms().find((t) => t.id === id)
182
+ if (!confirm(`Are you sure you want to delete "${current?.name || id}"?`)) return
183
+
184
+ try {
185
+ const res = await fetch(`/api/cms/taxonomies/${taxType()}/terms/${id}`, { method: "DELETE" })
186
+ if (!res.ok) throw new Error(`Failed to delete ${taxType()}`)
187
+ setTerms((prev) => prev.filter((t) => t.id !== id))
188
+ const remaining = terms().filter((t) => t.id !== id)
189
+ if (remaining.length > 0) {
190
+ selectTerm(remaining[0]!)
191
+ } else {
192
+ startNewTerm()
193
+ }
194
+ } catch (err: any) {
195
+ alert(err.message || `Failed to delete ${taxType()}`)
196
+ }
197
+ }
198
+
199
+ return (
200
+ <div class="flex flex-col gap-6 w-full max-w-7xl mx-auto py-2">
201
+ {/* Top Header */}
202
+ <div class="flex items-center justify-between border-b border-default pb-4">
203
+ <div>
204
+ <h1 class="text-2xl font-bold text-highlighted tracking-tight">
205
+ {props.title || defaultTitle()}
206
+ </h1>
207
+ <p class="text-xs text-muted mt-0.5">{props.description || defaultDesc()}</p>
208
+ </div>
209
+ </div>
210
+
211
+ {/* 2-Column Master-Detail Layout */}
212
+ <div class="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
213
+ {/* Left Column: Search & Terms List (5 Cols) */}
214
+ <div class="lg:col-span-5 flex flex-col gap-3">
215
+ <div class="flex items-center gap-2">
216
+ <div class="flex-1">
217
+ <RLSInput
218
+ value={searchQuery()}
219
+ onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
220
+ setSearchQuery(e.currentTarget.value)
221
+ }
222
+ placeholder={`Search ${props.title || defaultTitle().toLowerCase()}...`}
223
+ size="sm"
224
+ />
225
+ </div>
226
+ <RLSButton
227
+ variant="solid"
228
+ color="primary"
229
+ size="sm"
230
+ onClick={startNewTerm}
231
+ leadingIcon="plus"
232
+ label="New"
233
+ />
234
+ </div>
235
+
236
+ <Show when={isLoading()}>
237
+ <div class="p-8 text-center text-xs text-muted animate-pulse">Loading terms...</div>
238
+ </Show>
239
+
240
+ <Show when={error()}>
241
+ <div class="p-4 text-center text-xs text-error-500">{error()}</div>
242
+ </Show>
243
+
244
+ <Show when={!isLoading() && filteredTerms().length === 0}>
245
+ <div class="p-8 text-center border border-dashed border-default rounded-xl flex flex-col items-center gap-2">
246
+ <RLSIcon name="folder" size="md" class="text-muted/50" />
247
+ <span class="text-xs font-semibold text-highlighted">No terms found</span>
248
+ <p class="text-[11px] text-muted">Create a term to organize your content.</p>
249
+ </div>
250
+ </Show>
251
+
252
+ <div class="flex flex-col gap-2 max-h-[calc(100vh-280px)] overflow-y-auto pr-1">
253
+ <For each={filteredTerms()}>
254
+ {(item) => {
255
+ const isSelected = () => selectedId() === item.id
256
+ return (
257
+ <button
258
+ type="button"
259
+ onClick={() => selectTerm(item)}
260
+ class={`flex items-center justify-between p-3 rounded-xl border text-left transition cursor-pointer ${
261
+ isSelected()
262
+ ? "bg-elevated/90 border-primary shadow-sm"
263
+ : "bg-background/40 hover:bg-elevated/40 border-default"
264
+ }`}
265
+ >
266
+ <div class="flex items-center gap-2.5 min-w-0 flex-1">
267
+ <div class="w-8 h-8 rounded-lg bg-primary/10 text-primary font-bold text-xs flex items-center justify-center shrink-0">
268
+ {isCategory() ? "📁" : "#"}
269
+ </div>
270
+ <div class="flex flex-col min-w-0 flex-1">
271
+ <span class="text-sm font-semibold text-highlighted truncate">
272
+ {item.name}
273
+ </span>
274
+ <span class="text-xs font-mono text-muted truncate">{item.slug}</span>
275
+ </div>
276
+ </div>
277
+
278
+ <div class="flex items-center gap-2 shrink-0">
279
+ <span class="inline-flex items-center px-2 py-0.5 text-[11px] font-semibold rounded-full bg-elevated text-muted border border-default">
280
+ {item.postCount ?? 0} posts
281
+ </span>
282
+ <Show when={isSelected()}>
283
+ <RLSBadge variant="solid" color="primary" size="sm">
284
+ Active
285
+ </RLSBadge>
286
+ </Show>
287
+ </div>
288
+ </button>
289
+ )
290
+ }}
291
+ </For>
292
+ </div>
293
+ </div>
294
+
295
+ {/* Right Column: Edit / Create Form (7 Cols) */}
296
+ <div class="lg:col-span-7 bg-elevated/30 border border-default rounded-2xl p-6 flex flex-col gap-5">
297
+ <div class="flex items-center justify-between border-b border-default pb-3">
298
+ <h3 class="text-lg font-bold text-highlighted">
299
+ {selectedId()
300
+ ? `Edit ${isCategory() ? "category" : "tag"}`
301
+ : `Create ${isCategory() ? "category" : "tag"}`}
302
+ </h3>
303
+ <Show when={selectedId()}>
304
+ <span class="text-xs font-mono text-muted">ID: {selectedId()}</span>
305
+ </Show>
306
+ </div>
307
+
308
+ <form onSubmit={handleSave} class="flex flex-col gap-4">
309
+ {/* Name */}
310
+ <div class="flex flex-col gap-1.5">
311
+ <label class="text-xs font-semibold text-neutral-700">Display name *</label>
312
+ <RLSInput
313
+ value={form().name}
314
+ onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
315
+ handleNameChange(e.currentTarget.value)
316
+ }
317
+ placeholder={
318
+ isCategory() ? "e.g. Engineering, Tutorials" : "e.g. typescript, astro"
319
+ }
320
+ />
321
+ </div>
322
+
323
+ {/* Slug */}
324
+ <div class="flex flex-col gap-1.5">
325
+ <label class="text-xs font-semibold text-neutral-700">Slug</label>
326
+ <RLSInput
327
+ value={form().slug}
328
+ onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
329
+ setForm((prev) => ({ ...prev, slug: e.currentTarget.value }))
330
+ }
331
+ placeholder="engineering"
332
+ />
333
+ </div>
334
+
335
+ {/* Parent Category (if category) */}
336
+ <Show when={isCategory()}>
337
+ <div class="flex flex-col gap-1.5">
338
+ <label class="text-xs font-semibold text-neutral-700">Parent Category</label>
339
+ <select
340
+ value={form().parentId}
341
+ onChange={(e) =>
342
+ setForm((prev) => ({ ...prev, parentId: e.currentTarget.value }))
343
+ }
344
+ class="w-full text-sm py-2 px-3 rounded-lg border border-default bg-background text-highlighted focus:outline-none focus:ring-1 focus:ring-primary"
345
+ >
346
+ <option value="">(None - Top Level)</option>
347
+ <For each={terms().filter((t) => t.id !== selectedId())}>
348
+ {(cat) => <option value={cat.id}>{cat.name}</option>}
349
+ </For>
350
+ </select>
351
+ </div>
352
+ </Show>
353
+
354
+ {/* Description */}
355
+ <div class="flex flex-col gap-1.5">
356
+ <label class="text-xs font-semibold text-neutral-700">Description</label>
357
+ <textarea
358
+ value={form().description}
359
+ onInput={(e: InputEvent & { currentTarget: HTMLTextAreaElement }) =>
360
+ setForm((prev) => ({ ...prev, description: e.currentTarget.value }))
361
+ }
362
+ rows={3}
363
+ placeholder="Optional description..."
364
+ class="w-full text-sm py-2 px-3 rounded-lg border border-default bg-background text-highlighted focus:outline-none focus:ring-1 focus:ring-primary resize-none"
365
+ />
366
+ </div>
367
+
368
+ {/* Form Actions */}
369
+ <div class="flex items-center justify-between border-t border-default pt-4 mt-2">
370
+ <Show when={selectedId()} fallback={<div />}>
371
+ <RLSButton
372
+ type="button"
373
+ variant="outline"
374
+ color="neutral"
375
+ size="sm"
376
+ onClick={handleDelete}
377
+ label="Delete"
378
+ class="!text-error-500 hover:!text-error-600 !border-error-500/30"
379
+ />
380
+ </Show>
381
+
382
+ <div class="flex items-center gap-2">
383
+ <RLSButton
384
+ type="button"
385
+ variant="outline"
386
+ color="neutral"
387
+ size="sm"
388
+ onClick={startNewTerm}
389
+ label="Cancel"
390
+ />
391
+ <RLSButton
392
+ type="submit"
393
+ variant="solid"
394
+ color="primary"
395
+ size="sm"
396
+ disabled={isSaving()}
397
+ label={
398
+ isSaving()
399
+ ? "Saving..."
400
+ : selectedId()
401
+ ? "Save Changes"
402
+ : `Create ${isCategory() ? "Category" : "Tag"}`
403
+ }
404
+ />
405
+ </div>
406
+ </div>
407
+ </form>
408
+ </div>
409
+ </div>
410
+ </div>
411
+ )
412
+ }
@@ -122,8 +122,8 @@ export const RimelightTemplateEditor: Component<RimelightTemplateEditorProps> =
122
122
  <div class="cms-template-editor border border-default rounded-xl p-4 space-y-4">
123
123
  <div class="flex items-center justify-between p-3 rounded-lg border border-default">
124
124
  <div>
125
- <h4 class="text-sm font-semibold text-foreground">Initial Template Blocks</h4>
126
- <p class="text-xs text-muted-foreground">
125
+ <h4 class="text-sm font-semibold text-highlighted">Initial Template Blocks</h4>
126
+ <p class="text-xs text-muted">
127
127
  Visually design the initial layout and set block locks for pages instantiated from this
128
128
  template.
129
129
  </p>
@@ -156,7 +156,7 @@ export const RimelightTemplateEditor: Component<RimelightTemplateEditorProps> =
156
156
  class="space-y-3 min-h-[120px] p-3 border border-dashed border-default rounded-lg"
157
157
  >
158
158
  {blocks().length === 0 ? (
159
- <div class="text-center py-8 text-xs text-muted-foreground italic">
159
+ <div class="text-center py-8 text-xs text-muted italic">
160
160
  No initial blocks added yet. Use the dropdown above to add initial template blocks.
161
161
  </div>
162
162
  ) : (
@@ -173,8 +173,8 @@ export const RimelightTemplateEditor: Component<RimelightTemplateEditorProps> =
173
173
  >
174
174
  <div class="flex items-center justify-between border-b border-default/60 pb-2">
175
175
  <div class="flex items-center gap-2">
176
- <span class="font-bold text-foreground uppercase">{block.type}</span>
177
- <span class="text-[10px] text-muted-foreground">#{index() + 1}</span>
176
+ <span class="font-bold text-highlighted uppercase">{block.type}</span>
177
+ <span class="text-[10px] text-muted">#{index() + 1}</span>
178
178
  </div>
179
179
  <RLSButton
180
180
  variant="ghost"
@@ -212,9 +212,7 @@ export const RimelightTemplateEditor: Component<RimelightTemplateEditorProps> =
212
212
 
213
213
  <div>
214
214
  <div>
215
- <span class="block text-[11px] text-muted-foreground mb-1">
216
- Block Content / Text
217
- </span>
215
+ <span class="block text-[11px] text-muted mb-1">Block Content / Text</span>
218
216
  <div class="mt-1">
219
217
  <RLSInput
220
218
  id={`tmpl-content-${block.id}`}
@@ -105,9 +105,7 @@ export const CalloutEditor: Component<CalloutEditorProps> = (componentProps) =>
105
105
  <div class="w-full flex flex-col gap-3 p-3 rounded-xl border border-default/70">
106
106
  <div class="flex items-center justify-between gap-2">
107
107
  <div class="flex items-center gap-2">
108
- <span class="text-xs font-bold uppercase tracking-wider text-muted-foreground">
109
- Callout:
110
- </span>
108
+ <span class="text-xs font-bold uppercase tracking-wider text-muted">Callout:</span>
111
109
  <div class="w-36">
112
110
  <RLSSelect
113
111
  items={VARIANT_OPTIONS}
@@ -163,7 +161,7 @@ export const CalloutEditor: Component<CalloutEditorProps> = (componentProps) =>
163
161
  >
164
162
  {childrenBlocks().length === 0 ? (
165
163
  <div class="text-center py-2">
166
- <span class="text-xs text-muted-foreground block">Empty callout</span>
164
+ <span class="text-xs text-muted block">Empty callout</span>
167
165
  <RLSButton
168
166
  variant="link"
169
167
  color="primary"
@@ -80,9 +80,7 @@ export const CardsEditor: Component<CardsEditorProps> = (componentProps) => {
80
80
  <div class="rimelight-cards-editor flex flex-col gap-3 p-3 rounded-xl border border-default/70 w-full">
81
81
  <div class="flex items-center justify-between">
82
82
  <div class="flex items-center gap-2">
83
- <span class="text-xs font-bold uppercase tracking-wider text-muted-foreground">
84
- Cards Grid
85
- </span>
83
+ <span class="text-xs font-bold uppercase tracking-wider text-muted">Cards Grid</span>
86
84
  <div class="w-32">
87
85
  <RLSSelect
88
86
  items={COLUMN_OPTIONS}
@@ -110,7 +108,7 @@ export const CardsEditor: Component<CardsEditorProps> = (componentProps) => {
110
108
  >
111
109
  {childrenBlocks().length === 0 ? (
112
110
  <div class="text-center py-3">
113
- <span class="text-xs text-muted-foreground block mb-2">No cards in grid</span>
111
+ <span class="text-xs text-muted block mb-2">No cards in grid</span>
114
112
  <RLSButton
115
113
  variant="link"
116
114
  color="primary"
@@ -54,9 +54,7 @@ export const CodeEditor: Component<CodeEditorProps> = (componentProps) => {
54
54
  <div class="rimelight-code-editor flex flex-col gap-2 p-2 w-full">
55
55
  <div class="flex items-center justify-between mb-1">
56
56
  <div class="flex items-center gap-2">
57
- <span class="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
58
- Language
59
- </span>
57
+ <span class="text-xs font-semibold text-muted uppercase tracking-wide">Language</span>
60
58
  <div class="w-36">
61
59
  <RLSSelect
62
60
  items={LANGUAGES}
@@ -46,10 +46,8 @@ export const FileTreeEditor: Component<FileTreeEditorProps> = (componentProps) =
46
46
  return (
47
47
  <div class="rimelight-file-tree-editor flex flex-col gap-2 p-3 rounded-xl border border-default w-full">
48
48
  <div class="flex items-center justify-between">
49
- <span class="text-xs font-bold uppercase tracking-wider text-muted-foreground">
50
- File Tree JSON
51
- </span>
52
- <span class="text-xs text-muted-foreground italic">
49
+ <span class="text-xs font-bold uppercase tracking-wider text-muted">File Tree JSON</span>
50
+ <span class="text-xs text-muted italic">
53
51
  Format: Array of {`{ name, isDir, comment, children }`}
54
52
  </span>
55
53
  </div>
@@ -19,7 +19,7 @@ export const ImageEditor: Component<ImageEditorProps> = (componentProps) => {
19
19
  return (
20
20
  <div class="w-full flex flex-col gap-3 p-2">
21
21
  <div class="flex flex-col gap-1">
22
- <span class="text-xs font-bold text-muted-foreground uppercase">Image URL</span>
22
+ <span class="text-xs font-bold text-muted uppercase">Image URL</span>
23
23
  <RLSInput
24
24
  id="image-src-input"
25
25
  type="text"
@@ -32,7 +32,7 @@ export const ImageEditor: Component<ImageEditorProps> = (componentProps) => {
32
32
 
33
33
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
34
34
  <div class="flex flex-col gap-1">
35
- <span class="text-xs font-bold text-muted-foreground uppercase">Alt Text</span>
35
+ <span class="text-xs font-bold text-muted uppercase">Alt Text</span>
36
36
  <RLSInput
37
37
  id="image-alt-input"
38
38
  type="text"
@@ -44,7 +44,7 @@ export const ImageEditor: Component<ImageEditorProps> = (componentProps) => {
44
44
  </div>
45
45
 
46
46
  <div class="flex flex-col gap-1">
47
- <span class="text-xs font-bold text-muted-foreground uppercase">Caption</span>
47
+ <span class="text-xs font-bold text-muted uppercase">Caption</span>
48
48
  <RLSInput
49
49
  id="image-caption-input"
50
50
  type="text"
@@ -261,7 +261,7 @@ export const ParagraphEditor: Component<ParagraphEditorProps> = (componentProps)
261
261
  ref={(el) => (editableDiv = el)}
262
262
  aria-label="Paragraph text content"
263
263
  contentEditable={true}
264
- class="editable-div py-1.5 px-2 outline-none min-h-[2.5rem] text-sm text-foreground bg-transparent leading-relaxed focus:ring-1 focus:ring-primary/40 rounded-lg"
264
+ class="editable-div py-1.5 px-2 outline-none min-h-[2.5rem] text-sm text-highlighted bg-transparent leading-relaxed focus:ring-1 focus:ring-primary/40 rounded-lg"
265
265
  />
266
266
 
267
267
  {/* Link URL Modal — replaces browser prompt() */}
@@ -290,7 +290,7 @@ export const ParagraphEditor: Component<ParagraphEditorProps> = (componentProps)
290
290
  }
291
291
  >
292
292
  <div class="flex flex-col gap-2 py-2">
293
- <span class="text-xs font-semibold text-muted-foreground">URL</span>
293
+ <span class="text-xs font-semibold text-muted">URL</span>
294
294
  <RLSInput
295
295
  value={linkUrl()}
296
296
  onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
@@ -110,9 +110,7 @@ export const SceneEditor: Component<SceneEditorProps> = (componentProps) => {
110
110
 
111
111
  <div class="grid grid-cols-2 gap-3">
112
112
  <div class="col-span-2 flex flex-col gap-1">
113
- <span class="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
114
- Location
115
- </span>
113
+ <span class="text-xs font-semibold text-muted uppercase tracking-wide">Location</span>
116
114
  <RLSInput
117
115
  id="scene-location-input"
118
116
  data-field="location"
@@ -124,9 +122,7 @@ export const SceneEditor: Component<SceneEditorProps> = (componentProps) => {
124
122
  </div>
125
123
 
126
124
  <div class="flex flex-col gap-1">
127
- <span class="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
128
- Setting
129
- </span>
125
+ <span class="text-xs font-semibold text-muted uppercase tracking-wide">Setting</span>
130
126
  <RLSSelect
131
127
  items={SETTINGS}
132
128
  value={componentProps.props.setting || "INT."}
@@ -136,9 +132,7 @@ export const SceneEditor: Component<SceneEditorProps> = (componentProps) => {
136
132
  </div>
137
133
 
138
134
  <div class="flex flex-col gap-1">
139
- <span class="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
140
- Time of Day
141
- </span>
135
+ <span class="text-xs font-semibold text-muted uppercase tracking-wide">Time of Day</span>
142
136
  <RLSSelect
143
137
  items={TIMES_OF_DAY}
144
138
  value={componentProps.props.timeOfDay || "MORNING"}
@@ -149,7 +143,7 @@ export const SceneEditor: Component<SceneEditorProps> = (componentProps) => {
149
143
  </div>
150
144
 
151
145
  <div class="flex flex-col gap-1">
152
- <span class="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
146
+ <span class="text-xs font-semibold text-muted uppercase tracking-wide">
153
147
  Action / Description
154
148
  </span>
155
149
  <RLSTextarea
@@ -163,7 +157,7 @@ export const SceneEditor: Component<SceneEditorProps> = (componentProps) => {
163
157
  </div>
164
158
 
165
159
  <div class="flex flex-col gap-1">
166
- <span class="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
160
+ <span class="text-xs font-semibold text-muted uppercase tracking-wide">
167
161
  Transition (End of Scene)
168
162
  </span>
169
163
  <div class="w-48">
@@ -183,7 +177,7 @@ export const SceneEditor: Component<SceneEditorProps> = (componentProps) => {
183
177
  >
184
178
  {childrenBlocks().length === 0 ? (
185
179
  <div class="cms-empty-placeholder relative flex flex-col items-center justify-center gap-2 p-3 rounded-lg border border-dashed border-default text-center my-1">
186
- <div class="text-xs font-semibold text-muted-foreground">Empty scene</div>
180
+ <div class="text-xs font-semibold text-muted">Empty scene</div>
187
181
  <RLSButton
188
182
  variant="outline"
189
183
  color="neutral"