@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,438 @@
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 BylineEntity {
8
+ id: string
9
+ name: string
10
+ slug: string
11
+ websiteUrl?: string | null
12
+ bio?: string | null
13
+ avatar?: string | null
14
+ userId?: string | null
15
+ socials?: Record<string, string>
16
+ metadata?: Record<string, any>
17
+ createdAt?: string
18
+ }
19
+
20
+ export const RimelightBylinesManager: Component = () => {
21
+ const [bylines, setBylines] = createSignal<BylineEntity[]>([])
22
+ const [selectedId, setSelectedId] = createSignal<string | null>(null)
23
+ const [searchQuery, setSearchQuery] = createSignal("")
24
+ const [isLoading, setIsLoading] = createSignal(false)
25
+ const [isSaving, setIsSaving] = createSignal(false)
26
+ const [error, setError] = createSignal<string | null>(null)
27
+
28
+ // Form State
29
+ const [form, setForm] = createSignal<{
30
+ name: string
31
+ slug: string
32
+ websiteUrl: string
33
+ bio: string
34
+ avatar: string
35
+ userId: string
36
+ }>({
37
+ name: "",
38
+ slug: "",
39
+ websiteUrl: "",
40
+ bio: "",
41
+ avatar: "",
42
+ userId: ""
43
+ })
44
+
45
+ const loadBylines = async () => {
46
+ setIsLoading(true)
47
+ setError(null)
48
+ try {
49
+ const res = await fetch("/api/cms/bylines")
50
+ if (!res.ok) throw new Error("Failed to load bylines")
51
+ const data = await res.json()
52
+ const list: BylineEntity[] = Array.isArray(data) ? data : []
53
+ setBylines(list)
54
+
55
+ if (list.length > 0 && !selectedId()) {
56
+ selectByline(list[0]!)
57
+ } else if (list.length === 0) {
58
+ startNewByline()
59
+ }
60
+ } catch (err: any) {
61
+ setError(err.message || "Failed to load bylines")
62
+ } finally {
63
+ setIsLoading(false)
64
+ }
65
+ }
66
+
67
+ onMount(() => {
68
+ void loadBylines()
69
+ })
70
+
71
+ const filteredBylines = createMemo(() => {
72
+ const q = searchQuery().toLowerCase().trim()
73
+ if (!q) return bylines()
74
+ return bylines().filter(
75
+ (b) =>
76
+ b.name.toLowerCase().includes(q) ||
77
+ b.slug.toLowerCase().includes(q) ||
78
+ (b.bio && b.bio.toLowerCase().includes(q))
79
+ )
80
+ })
81
+
82
+ const selectByline = (byline: BylineEntity) => {
83
+ setSelectedId(byline.id)
84
+ setForm({
85
+ name: byline.name || "",
86
+ slug: byline.slug || "",
87
+ websiteUrl: byline.websiteUrl || "",
88
+ bio: byline.bio || "",
89
+ avatar: byline.avatar || "",
90
+ userId: byline.userId || ""
91
+ })
92
+ }
93
+
94
+ const startNewByline = () => {
95
+ setSelectedId(null)
96
+ setForm({
97
+ name: "",
98
+ slug: "",
99
+ websiteUrl: "",
100
+ bio: "",
101
+ avatar: "",
102
+ userId: ""
103
+ })
104
+ }
105
+
106
+ const handleNameChange = (name: string) => {
107
+ const isNew = !selectedId()
108
+ const autoSlug = isNew
109
+ ? name
110
+ .toLowerCase()
111
+ .replace(/[^a-z0-9]+/g, "-")
112
+ .replace(/(^-|-$)/g, "")
113
+ : form().slug
114
+
115
+ setForm((prev) => ({
116
+ ...prev,
117
+ name,
118
+ slug: isNew ? autoSlug : prev.slug
119
+ }))
120
+ }
121
+
122
+ const handleSave = async (e?: Event) => {
123
+ e?.preventDefault()
124
+ const f = form()
125
+ if (!f.name.trim()) {
126
+ alert("Display name is required")
127
+ return
128
+ }
129
+
130
+ const slug =
131
+ f.slug.trim() ||
132
+ f.name
133
+ .toLowerCase()
134
+ .replace(/[^a-z0-9]+/g, "-")
135
+ .replace(/(^-|-$)/g, "")
136
+
137
+ const payload = {
138
+ name: f.name.trim(),
139
+ slug,
140
+ websiteUrl: f.websiteUrl.trim() || null,
141
+ bio: f.bio.trim() || null,
142
+ avatar: f.avatar.trim() || null,
143
+ userId: f.userId.trim() || null
144
+ }
145
+
146
+ setIsSaving(true)
147
+ try {
148
+ if (selectedId()) {
149
+ const res = await fetch(`/api/cms/bylines/${selectedId()}`, {
150
+ method: "PUT",
151
+ headers: { "Content-Type": "application/json" },
152
+ body: JSON.stringify(payload)
153
+ })
154
+ if (!res.ok) throw new Error("Failed to update byline")
155
+ const updated = await res.json()
156
+ setBylines((prev) => prev.map((b) => (b.id === updated.id ? updated : b)))
157
+ } else {
158
+ const res = await fetch("/api/cms/bylines", {
159
+ method: "POST",
160
+ headers: { "Content-Type": "application/json" },
161
+ body: JSON.stringify(payload)
162
+ })
163
+ if (!res.ok) throw new Error("Failed to create byline")
164
+ const created = await res.json()
165
+ setBylines((prev) => [created, ...prev])
166
+ setSelectedId(created.id)
167
+ }
168
+ } catch (err: any) {
169
+ alert(err.message || "Failed to save byline")
170
+ } finally {
171
+ setIsSaving(false)
172
+ }
173
+ }
174
+
175
+ const handleDelete = async () => {
176
+ const id = selectedId()
177
+ if (!id) return
178
+ const current = bylines().find((b) => b.id === id)
179
+ if (!confirm(`Are you sure you want to delete byline "${current?.name || id}"?`)) return
180
+
181
+ try {
182
+ const res = await fetch(`/api/cms/bylines/${id}`, { method: "DELETE" })
183
+ if (!res.ok) throw new Error("Failed to delete byline")
184
+ setBylines((prev) => prev.filter((b) => b.id !== id))
185
+ const remaining = bylines().filter((b) => b.id !== id)
186
+ if (remaining.length > 0) {
187
+ selectByline(remaining[0]!)
188
+ } else {
189
+ startNewByline()
190
+ }
191
+ } catch (err: any) {
192
+ alert(err.message || "Failed to delete byline")
193
+ }
194
+ }
195
+
196
+ return (
197
+ <div class="flex flex-col gap-6 w-full max-w-7xl mx-auto py-2">
198
+ {/* Top Header */}
199
+ <div class="flex items-center justify-between border-b border-default pb-4">
200
+ <div>
201
+ <h1 class="text-2xl font-bold text-highlighted tracking-tight">Bylines</h1>
202
+ <p class="text-xs text-muted mt-0.5">
203
+ Manage author profiles and multi-author attributions across articles and pages.
204
+ </p>
205
+ </div>
206
+ </div>
207
+
208
+ {/* 2-Column Master-Detail Layout */}
209
+ <div class="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
210
+ {/* Left Column: Search & Byline Cards List (5 Cols) */}
211
+ <div class="lg:col-span-5 flex flex-col gap-3">
212
+ <div class="flex items-center gap-2">
213
+ <div class="flex-1">
214
+ <RLSInput
215
+ value={searchQuery()}
216
+ onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
217
+ setSearchQuery(e.currentTarget.value)
218
+ }
219
+ placeholder="Search bylines..."
220
+ size="sm"
221
+ />
222
+ </div>
223
+ <RLSButton
224
+ variant="solid"
225
+ color="primary"
226
+ size="sm"
227
+ onClick={startNewByline}
228
+ leadingIcon="plus"
229
+ label="New"
230
+ />
231
+ </div>
232
+
233
+ <Show when={isLoading()}>
234
+ <div class="p-8 text-center text-xs text-muted animate-pulse">Loading bylines...</div>
235
+ </Show>
236
+
237
+ <Show when={error()}>
238
+ <div class="p-4 text-center text-xs text-error-500">{error()}</div>
239
+ </Show>
240
+
241
+ <Show when={!isLoading() && filteredBylines().length === 0}>
242
+ <div class="p-8 text-center border border-dashed border-default rounded-xl flex flex-col items-center gap-2">
243
+ <RLSIcon name="user" size="md" class="text-muted/50" />
244
+ <span class="text-xs font-semibold text-highlighted">No bylines found</span>
245
+ <p class="text-[11px] text-muted">
246
+ Create a byline to attribute authors to your posts.
247
+ </p>
248
+ </div>
249
+ </Show>
250
+
251
+ <div class="flex flex-col gap-2 max-h-[calc(100vh-280px)] overflow-y-auto pr-1">
252
+ <For each={filteredBylines()}>
253
+ {(item) => {
254
+ const isSelected = () => selectedId() === item.id
255
+ return (
256
+ <button
257
+ type="button"
258
+ onClick={() => selectByline(item)}
259
+ class={`flex items-center gap-3 p-3 rounded-xl border text-left transition cursor-pointer ${
260
+ isSelected()
261
+ ? "bg-elevated/90 border-primary shadow-sm"
262
+ : "bg-background/40 hover:bg-elevated/40 border-default"
263
+ }`}
264
+ >
265
+ {item.avatar ? (
266
+ <img
267
+ src={item.avatar}
268
+ alt={item.name}
269
+ class="w-10 h-10 rounded-full object-cover shrink-0 border border-default"
270
+ />
271
+ ) : (
272
+ <div class="w-10 h-10 rounded-full bg-primary/10 text-primary font-bold text-sm flex items-center justify-center shrink-0 border border-primary/20">
273
+ {item.name.charAt(0).toUpperCase()}
274
+ </div>
275
+ )}
276
+ <div class="flex flex-col min-w-0 flex-1">
277
+ <div class="flex items-center justify-between gap-1">
278
+ <span class="text-sm font-semibold text-highlighted truncate">
279
+ {item.name}
280
+ </span>
281
+ <Show when={isSelected()}>
282
+ <RLSBadge variant="solid" color="primary" size="sm">
283
+ Active
284
+ </RLSBadge>
285
+ </Show>
286
+ </div>
287
+ <span class="text-xs font-mono text-muted truncate">{item.slug}</span>
288
+ </div>
289
+ </button>
290
+ )
291
+ }}
292
+ </For>
293
+ </div>
294
+ </div>
295
+
296
+ {/* Right Column: Edit / Create Form (7 Cols) */}
297
+ <div class="lg:col-span-7 bg-elevated/30 border border-default rounded-2xl p-6 flex flex-col gap-5">
298
+ <div class="flex items-center justify-between border-b border-default pb-3">
299
+ <h3 class="text-lg font-bold text-highlighted">
300
+ {selectedId() ? "Edit byline" : "Create byline"}
301
+ </h3>
302
+ <Show when={selectedId()}>
303
+ <span class="text-xs font-mono text-muted">ID: {selectedId()}</span>
304
+ </Show>
305
+ </div>
306
+
307
+ <form onSubmit={handleSave} class="flex flex-col gap-4">
308
+ {/* Display Name */}
309
+ <div class="flex flex-col gap-1.5">
310
+ <label class="text-xs font-semibold text-neutral-700">Display name *</label>
311
+ <RLSInput
312
+ value={form().name}
313
+ onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
314
+ handleNameChange(e.currentTarget.value)
315
+ }
316
+ placeholder="e.g. Guest Contributor, EmDash Editorial"
317
+ />
318
+ </div>
319
+
320
+ {/* Slug */}
321
+ <div class="flex flex-col gap-1.5">
322
+ <label class="text-xs font-semibold text-neutral-700">Slug</label>
323
+ <RLSInput
324
+ value={form().slug}
325
+ onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
326
+ setForm((prev) => ({ ...prev, slug: e.currentTarget.value }))
327
+ }
328
+ placeholder="guest-contributor"
329
+ />
330
+ </div>
331
+
332
+ {/* Website URL */}
333
+ <div class="flex flex-col gap-1.5">
334
+ <label class="text-xs font-semibold text-neutral-700">Website URL</label>
335
+ <RLSInput
336
+ value={form().websiteUrl}
337
+ onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
338
+ setForm((prev) => ({ ...prev, websiteUrl: e.currentTarget.value }))
339
+ }
340
+ placeholder="https://..."
341
+ />
342
+ </div>
343
+
344
+ {/* Bio */}
345
+ <div class="flex flex-col gap-1.5">
346
+ <label class="text-xs font-semibold text-neutral-700">Bio</label>
347
+ <textarea
348
+ value={form().bio}
349
+ onInput={(e: InputEvent & { currentTarget: HTMLTextAreaElement }) =>
350
+ setForm((prev) => ({ ...prev, bio: e.currentTarget.value }))
351
+ }
352
+ rows={3}
353
+ placeholder="Short author bio..."
354
+ 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"
355
+ />
356
+ </div>
357
+
358
+ {/* Avatar URL & Preview */}
359
+ <div class="flex flex-col gap-1.5">
360
+ <label class="text-xs font-semibold text-neutral-700">Avatar Image URL</label>
361
+ <div class="flex items-center gap-3">
362
+ <Show
363
+ when={form().avatar}
364
+ fallback={
365
+ <div class="w-12 h-12 rounded-xl border border-dashed border-default flex items-center justify-center text-muted/50 shrink-0">
366
+ <RLSIcon name="image" size="md" />
367
+ </div>
368
+ }
369
+ >
370
+ <img
371
+ src={form().avatar}
372
+ alt="Preview"
373
+ class="w-12 h-12 rounded-xl object-cover border border-default shrink-0"
374
+ />
375
+ </Show>
376
+ <div class="flex-1">
377
+ <RLSInput
378
+ value={form().avatar}
379
+ onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
380
+ setForm((prev) => ({ ...prev, avatar: e.currentTarget.value }))
381
+ }
382
+ placeholder="https://... / avatar.png"
383
+ />
384
+ </div>
385
+ </div>
386
+ </div>
387
+
388
+ {/* Linked User ID */}
389
+ <div class="flex flex-col gap-1.5">
390
+ <label class="text-xs font-semibold text-neutral-700">Linked user</label>
391
+ <RLSInput
392
+ value={form().userId}
393
+ onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
394
+ setForm((prev) => ({ ...prev, userId: e.currentTarget.value }))
395
+ }
396
+ placeholder="Optional system User ID or email"
397
+ />
398
+ </div>
399
+
400
+ {/* Form Actions */}
401
+ <div class="flex items-center justify-between border-t border-default pt-4 mt-2">
402
+ <Show when={selectedId()} fallback={<div />}>
403
+ <RLSButton
404
+ type="button"
405
+ variant="outline"
406
+ color="neutral"
407
+ size="sm"
408
+ onClick={handleDelete}
409
+ label="Delete"
410
+ class="!text-error-500 hover:!text-error-600 !border-error-500/30"
411
+ />
412
+ </Show>
413
+
414
+ <div class="flex items-center gap-2">
415
+ <RLSButton
416
+ type="button"
417
+ variant="outline"
418
+ color="neutral"
419
+ size="sm"
420
+ onClick={startNewByline}
421
+ label="Cancel"
422
+ />
423
+ <RLSButton
424
+ type="submit"
425
+ variant="solid"
426
+ color="primary"
427
+ size="sm"
428
+ disabled={isSaving()}
429
+ label={isSaving() ? "Saving..." : selectedId() ? "Save Changes" : "Create Byline"}
430
+ />
431
+ </div>
432
+ </div>
433
+ </form>
434
+ </div>
435
+ </div>
436
+ </div>
437
+ )
438
+ }
@@ -65,9 +65,9 @@ export const RimelightEditor: Component<RimelightEditorProps> = (props) => {
65
65
  <div ref={canvasContainer} class="flex flex-col gap-3 w-full min-h-[200px] pl-6">
66
66
  {props.state.blocks.length === 0 ? (
67
67
  <div class="flex flex-col items-center justify-center p-8 sm:p-12 rounded-xl border border-dashed border-default text-center my-4">
68
- <div class="text-3xl text-muted-foreground mb-2">📄</div>
69
- <h4 class="text-base font-semibold text-foreground">No blocks yet</h4>
70
- <p class="text-xs text-muted-foreground mt-1 max-w-xs">
68
+ <div class="text-3xl text-muted mb-2">📄</div>
69
+ <h4 class="text-base font-semibold text-highlighted">No blocks yet</h4>
70
+ <p class="text-xs text-muted mt-1 max-w-xs">
71
71
  Start building your page content by adding your first block below.
72
72
  </p>
73
73
  <div class="mt-4">
@@ -85,7 +85,7 @@ export const RimelightPreview: Component<RimelightPreviewProps> = (props) => {
85
85
  </span>
86
86
  )}
87
87
  {title() && (
88
- <h1 class="text-3xl sm:text-4xl font-extrabold text-foreground tracking-tight leading-tight">
88
+ <h1 class="text-3xl sm:text-4xl font-extrabold text-highlighted tracking-tight leading-tight">
89
89
  {title()}
90
90
  </h1>
91
91
  )}
@@ -150,11 +150,11 @@ function renderBlockPreviewHtml(b: BaseBlock, sectionDepth: number = 0): string
150
150
  if (node.type === "link") {
151
151
  const inner = node.children ? renderNodes(node.children) : escapeHtml(node.text || "")
152
152
  const target = node.openInNewTab ? ' target="_blank" rel="noopener noreferrer"' : ""
153
- return `<a href="${escapeHtml(node.url || "#")}"${target} class="text-blue-500 hover:text-blue-400 underline underline-offset-2">${inner}</a>`
153
+ return `<a href="${escapeHtml(node.url || "#")}"${target} class="text-info-500 hover:text-info-400 underline underline-offset-2">${inner}</a>`
154
154
  }
155
155
  if (node.type === "page_mention") {
156
156
  const title = escapeHtml(node.displayTitle || node.pageSlug || node.pageId || "Page")
157
- return `<span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-400 text-sm font-medium">📄 ${title}</span>`
157
+ return `<span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-info-500/10 text-info-400 text-sm font-medium">📄 ${title}</span>`
158
158
  }
159
159
  return ""
160
160
  })
@@ -167,7 +167,7 @@ function renderBlockPreviewHtml(b: BaseBlock, sectionDepth: number = 0): string
167
167
  } else {
168
168
  html = escapeHtml(textVal)
169
169
  }
170
- return `<p class="text-neutral-800 dark:text-neutral-200 text-base leading-relaxed my-3 whitespace-pre-wrap break-words">${html || '<span class="text-neutral-500 italic">Empty paragraph</span>'}</p>`
170
+ return `<p class="text-neutral-800 text-base leading-relaxed my-3 whitespace-pre-wrap break-words">${html || '<span class="text-neutral-500 italic">Empty paragraph</span>'}</p>`
171
171
  }
172
172
 
173
173
  if (b.type === "SectionBlock") {
@@ -175,9 +175,8 @@ function renderBlockPreviewHtml(b: BaseBlock, sectionDepth: number = 0): string
175
175
  const title = props.title || "Untitled Section"
176
176
  const slug = title.toLowerCase().replace(/\W+/g, "-")
177
177
  const description = props.description
178
- let headingClasses = "font-bold text-neutral-900 dark:text-white"
179
- if (level === 2)
180
- headingClasses += " text-3xl mt-6 mb-3 border-b border-gray-200 dark:border-gray-800 pb-2"
178
+ let headingClasses = "font-bold text-neutral-900"
179
+ if (level === 2) headingClasses += " text-3xl mt-6 mb-3 border-b border-neutral-200 pb-2"
181
180
  else if (level === 3) headingClasses += " text-2xl font-semibold mt-5 mb-2.5"
182
181
  else if (level === 4) headingClasses += " text-xl font-medium mt-4 mb-2"
183
182
  else if (level === 5) headingClasses += " text-lg font-medium mt-3 mb-1.5"
@@ -185,8 +184,8 @@ function renderBlockPreviewHtml(b: BaseBlock, sectionDepth: number = 0): string
185
184
 
186
185
  return `<div class="section-block my-6">
187
186
  <h${level} id="${slug}" class="${headingClasses}">${escapeHtml(title)}</h${level}>
188
- ${description ? `<p class="text-gray-500 dark:text-gray-400 text-sm mt-1 mb-4 italic">${escapeHtml(description)}</p>` : ""}
189
- <div class="mt-4 pl-4 border-l-2 border-gray-200 dark:border-gray-800">
187
+ ${description ? `<p class="text-neutral-500 text-sm mt-1 mb-4 italic">${escapeHtml(description)}</p>` : ""}
188
+ <div class="mt-4 pl-4 border-l-2 border-neutral-200">
190
189
  ${getChildren(b)
191
190
  .map((c) => renderBlockPreviewHtml(c, sectionDepth + 1))
192
191
  .join("")}
@@ -209,26 +208,19 @@ function renderBlockPreviewHtml(b: BaseBlock, sectionDepth: number = 0): string
209
208
  }
210
209
  const icon = icons[variant] || icons.info
211
210
 
212
- let variantClasses =
213
- "bg-blue-50/50 dark:bg-blue-950/10 border-blue-200 dark:border-blue-900/50 text-blue-800 dark:text-blue-200"
211
+ let variantClasses = "bg-info-50/50 border-info-200 text-info-800"
214
212
  if (variant === "success") {
215
- variantClasses =
216
- "bg-green-50/50 dark:bg-green-950/10 border-green-200 dark:border-green-900/50 text-green-800 dark:text-green-200"
213
+ variantClasses = "bg-success-50/50 border-success-200 text-success-800"
217
214
  } else if (variant === "warning") {
218
- variantClasses =
219
- "bg-yellow-50/50 dark:bg-yellow-950/10 border-yellow-200 dark:border-yellow-900/50 text-yellow-800 dark:text-yellow-200"
215
+ variantClasses = "bg-warning-50/50 border-warning-200 text-warning-800"
220
216
  } else if (variant === "error") {
221
- variantClasses =
222
- "bg-red-50/50 dark:bg-red-950/10 border-red-200 dark:border-red-900/50 text-red-800 dark:text-red-200"
217
+ variantClasses = "bg-error-50/50 border-error-200 text-error-800"
223
218
  } else if (variant === "commentary") {
224
- variantClasses =
225
- "bg-purple-50/50 dark:bg-purple-950/10 border-purple-200 dark:border-purple-900/50 text-purple-800 dark:text-purple-200"
219
+ variantClasses = "bg-ideation-50/50 border-ideation-200 text-ideation-800"
226
220
  } else if (variant === "ideation") {
227
- variantClasses =
228
- "bg-amber-50/50 dark:bg-amber-950/10 border-amber-200 dark:border-amber-900/50 text-amber-800 dark:text-amber-200"
221
+ variantClasses = "bg-warning-50/50 border-warning-200 text-warning-800"
229
222
  } else if (variant === "source") {
230
- variantClasses =
231
- "bg-indigo-50/50 dark:bg-indigo-950/10 border-indigo-200 dark:border-indigo-900/50 text-indigo-800 dark:text-indigo-200"
223
+ variantClasses = "bg-info-50/50 border-info-200 text-info-800"
232
224
  }
233
225
 
234
226
  const tag = to ? `a href="${escapeHtml(to)}" target="${escapeHtml(target)}"` : "div"
@@ -249,8 +241,8 @@ function renderBlockPreviewHtml(b: BaseBlock, sectionDepth: number = 0): string
249
241
  const alt = props.alt || "Image"
250
242
  const caption = props.caption
251
243
  return `<figure class="mx-auto my-6 flex flex-col items-center max-w-full">
252
- <img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" class="h-auto max-w-full rounded-xl object-cover border border-neutral-200 dark:border-neutral-800 shadow-sm" />
253
- ${caption ? `<figcaption class="mt-2 text-center text-xs text-neutral-500 dark:text-neutral-400 italic">${escapeHtml(caption)}</figcaption>` : ""}
244
+ <img src="${escapeHtml(src)}" alt="${escapeHtml(alt)}" class="h-auto max-w-full rounded-xl object-cover border border-neutral-200 shadow-sm" />
245
+ ${caption ? `<figcaption class="mt-2 text-center text-xs text-neutral-500 italic">${escapeHtml(caption)}</figcaption>` : ""}
254
246
  </figure>`
255
247
  }
256
248
 
@@ -258,10 +250,10 @@ function renderBlockPreviewHtml(b: BaseBlock, sectionDepth: number = 0): string
258
250
  const code = props.code || textVal
259
251
  const language = props.language || "plaintext"
260
252
  const caption = props.caption
261
- return `<div class="cms-code my-4 rounded-lg bg-gray-950 p-4 font-mono text-sm border border-gray-800 overflow-x-auto">
262
- <div class="text-xs text-gray-500 uppercase mb-2 select-none">${escapeHtml(language)}</div>
263
- <pre class="m-0"><code class="text-green-300 font-mono">${escapeHtml(code)}</code></pre>
264
- ${caption ? `<div class="mt-2 text-center text-xs text-gray-500 italic">${escapeHtml(caption)}</div>` : ""}
253
+ return `<div class="cms-code my-4 rounded-lg bg-neutral-950 p-4 font-mono text-sm border border-neutral-800 overflow-x-auto">
254
+ <div class="text-xs text-neutral-500 uppercase mb-2 select-none">${escapeHtml(language)}</div>
255
+ <pre class="m-0"><code class="text-success-300 font-mono">${escapeHtml(code)}</code></pre>
256
+ ${caption ? `<div class="mt-2 text-center text-xs text-neutral-500 italic">${escapeHtml(caption)}</div>` : ""}
265
257
  </div>`
266
258
  }
267
259
 
@@ -270,22 +262,22 @@ function renderBlockPreviewHtml(b: BaseBlock, sectionDepth: number = 0): string
270
262
  const synopsis = props.synopsis
271
263
  const chars = props.characters || []
272
264
  const { aPlot, bPlot, cPlot, stinger } = props
273
- return `<div class="my-6 p-4 border-l-4 border-amber-400 bg-amber-50/20 dark:bg-amber-950/10 rounded-r-lg">
265
+ return `<div class="my-6 p-4 border-l-4 border-warning-400 bg-warning-50/20 rounded-r-lg">
274
266
  <div class="mb-4">
275
- <h3 class="text-xl font-bold text-amber-900 dark:text-amber-100">${escapeHtml(title)}</h3>
276
- ${synopsis ? `<p class="text-sm text-gray-500 dark:text-gray-400 mt-1 italic">${escapeHtml(synopsis)}</p>` : ""}
277
- ${chars.length ? `<div class="flex flex-wrap gap-3 mt-3 text-xs">${chars.map((c: string) => `<span class="px-2 py-0.5 rounded-full bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-200">${escapeHtml(c)}</span>`).join("")}</div>` : ""}
267
+ <h3 class="text-xl font-bold text-warning-900">${escapeHtml(title)}</h3>
268
+ ${synopsis ? `<p class="text-sm text-neutral-500 mt-1 italic">${escapeHtml(synopsis)}</p>` : ""}
269
+ ${chars.length ? `<div class="flex flex-wrap gap-3 mt-3 text-xs">${chars.map((c: string) => `<span class="px-2 py-0.5 rounded-full bg-warning-100 text-warning-800">${escapeHtml(c)}</span>`).join("")}</div>` : ""}
278
270
  </div>
279
271
  ${
280
272
  aPlot || bPlot || cPlot
281
273
  ? `<div class="grid grid-cols-1 md:grid-cols-3 gap-3 my-3">
282
- ${aPlot ? `<div class="bg-white/50 dark:bg-neutral-900/30 rounded p-2 border border-gray-200 dark:border-gray-700"><div class="text-[10px] font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400">A Plot</div><div class="text-sm mt-0.5">${escapeHtml(aPlot)}</div></div>` : ""}
283
- ${bPlot ? `<div class="bg-white/50 dark:bg-neutral-900/30 rounded p-2 border border-gray-200 dark:border-gray-700"><div class="text-[10px] font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400">B Plot</div><div class="text-sm mt-0.5">${escapeHtml(bPlot)}</div></div>` : ""}
284
- ${cPlot ? `<div class="bg-white/50 dark:bg-neutral-900/30 rounded p-2 border border-gray-200 dark:border-gray-700"><div class="text-[10px] font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400">C Plot</div><div class="text-sm mt-0.5">${escapeHtml(cPlot)}</div></div>` : ""}
274
+ ${aPlot ? `<div class="bg-neutral-50/50 rounded p-2 border border-neutral-200"><div class="text-[10px] font-bold uppercase tracking-wider text-neutral-500">A Plot</div><div class="text-sm mt-0.5">${escapeHtml(aPlot)}</div></div>` : ""}
275
+ ${bPlot ? `<div class="bg-neutral-50/50 rounded p-2 border border-neutral-200"><div class="text-[10px] font-bold uppercase tracking-wider text-neutral-500">B Plot</div><div class="text-sm mt-0.5">${escapeHtml(bPlot)}</div></div>` : ""}
276
+ ${cPlot ? `<div class="bg-neutral-50/50 rounded p-2 border border-neutral-200"><div class="text-[10px] font-bold uppercase tracking-wider text-neutral-500">C Plot</div><div class="text-sm mt-0.5">${escapeHtml(cPlot)}</div></div>` : ""}
285
277
  </div>`
286
278
  : ""
287
279
  }
288
- ${stinger ? `<p class="text-sm text-gray-500 dark:text-gray-400 italic mt-2 border-t border-gray-200 dark:border-gray-700 pt-2">Stinger: ${escapeHtml(stinger)}</p>` : ""}
280
+ ${stinger ? `<p class="text-sm text-neutral-500 italic mt-2 border-t border-neutral-200 pt-2">Stinger: ${escapeHtml(stinger)}</p>` : ""}
289
281
  <div class="mt-4">
290
282
  ${getChildren(b)
291
283
  .map((c) => renderBlockPreviewHtml(c, sectionDepth))
@@ -325,12 +317,12 @@ function renderBlockPreviewHtml(b: BaseBlock, sectionDepth: number = 0): string
325
317
  if (location) parts.push(location)
326
318
  if (tod && tod !== "Other") parts.push(`- ${tod}`)
327
319
 
328
- return `<div class="my-4 p-3 border-l-4 border-indigo-400 bg-indigo-50/20 dark:bg-indigo-950/10 rounded-r-lg">
329
- <div class="flex flex-wrap items-center gap-2 text-sm font-semibold text-indigo-800 dark:text-indigo-200 mb-2">
320
+ return `<div class="my-4 p-3 border-l-4 border-info-400 bg-info-50/20 rounded-r-lg">
321
+ <div class="flex flex-wrap items-center gap-2 text-sm font-semibold text-info-800 mb-2">
330
322
  ${parts.map((p) => `<span>${escapeHtml(p)}</span>`).join("")}
331
- ${transition ? `<span class="ml-auto text-[10px] italic text-gray-500 dark:text-gray-400">${escapeHtml(transition)}</span>` : ""}
323
+ ${transition ? `<span class="ml-auto text-[10px] italic text-neutral-500">${escapeHtml(transition)}</span>` : ""}
332
324
  </div>
333
- ${props.description ? `<p class="text-sm text-gray-700 dark:text-gray-300 mb-2">${escapeHtml(props.description)}</p>` : ""}
325
+ ${props.description ? `<p class="text-sm text-neutral-700 mb-2">${escapeHtml(props.description)}</p>` : ""}
334
326
  <div class="mt-3">
335
327
  ${getChildren(b)
336
328
  .map((c) => renderBlockPreviewHtml(c, sectionDepth))
@@ -344,11 +336,11 @@ function renderBlockPreviewHtml(b: BaseBlock, sectionDepth: number = 0): string
344
336
  const parenthetical = props.parenthetical
345
337
  const line = props.line || ""
346
338
  return `<div class="my-3 mx-8 max-w-md">
347
- <div class="text-sm font-bold text-center text-pink-800 dark:text-pink-200">${escapeHtml(character)}</div>
348
- ${parenthetical ? `<div class="text-xs italic text-center text-gray-500 dark:text-gray-400 mt-0.5">(${escapeHtml(parenthetical)})</div>` : ""}
349
- <div class="text-sm text-center text-gray-700 dark:text-gray-300 mt-1">${escapeHtml(line)}</div>
339
+ <div class="text-sm font-bold text-center text-commentary-800">${escapeHtml(character)}</div>
340
+ ${parenthetical ? `<div class="text-xs italic text-center text-neutral-500 mt-0.5">(${escapeHtml(parenthetical)})</div>` : ""}
341
+ <div class="text-sm text-center text-neutral-700 mt-1">${escapeHtml(line)}</div>
350
342
  </div>`
351
343
  }
352
344
 
353
- return `<div class="p-3 bg-gray-900 border border-gray-800 rounded text-sm text-gray-400 my-2">${escapeHtml(b.type)}: ${escapeHtml(textVal)}</div>`
345
+ return `<div class="p-3 bg-neutral-900 border border-neutral-800 rounded text-sm text-neutral-400 my-2">${escapeHtml(b.type)}: ${escapeHtml(textVal)}</div>`
354
346
  }