@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.
- 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 -102
- 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 -86
- package/src/core/types/blocks.ts +6 -1
- 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 -8
- 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 -4
- 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/ApiDocumentationBlock.astro +0 -350
- package/src/astro/blocks/DialogueBlock.astro +0 -23
- package/src/astro/blocks/LivePreviewBlock.astro +0 -27
- 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-tools.ts +0 -334
- package/src/migration.ts +0 -155
- package/src/schema/component_dependencies.ts +0 -21
- package/src/schema/component_metadata.ts +0 -31
- package/src/schema/component_metadata_cache.ts +0 -15
- package/src/schema/component_versions.ts +0 -19
- package/src/search/sync.ts +0 -32
- package/src/services/component-metadata.ts +0 -134
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
getPageDefinition,
|
|
4
4
|
type PageDefinition,
|
|
5
5
|
type FieldDefinition
|
|
6
|
-
} from "../../core/page-definitions"
|
|
6
|
+
} from "../../core/page-definitions.ts"
|
|
7
7
|
import RLSInput from "@rimelight/ui/components/input/RLSInput.tsx"
|
|
8
8
|
import RLSSelect from "@rimelight/ui/components/select/RLSSelect.tsx"
|
|
9
9
|
import RLSButton from "@rimelight/ui/components/button/RLSButton.tsx"
|
|
@@ -18,6 +18,13 @@ function getTextValue(value: any): string {
|
|
|
18
18
|
return ""
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export interface BylineItem {
|
|
22
|
+
name: string
|
|
23
|
+
avatar?: string
|
|
24
|
+
role?: string
|
|
25
|
+
url?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
export interface RimelightPropertiesPanelProps {
|
|
22
29
|
pageType?: string
|
|
23
30
|
initialProperties?: Record<string, any>
|
|
@@ -25,6 +32,7 @@ export interface RimelightPropertiesPanelProps {
|
|
|
25
32
|
slug?: string
|
|
26
33
|
icon?: string
|
|
27
34
|
tags?: string[]
|
|
35
|
+
bylines?: BylineItem[]
|
|
28
36
|
onPropertiesChange?: (props: Record<string, any>) => void
|
|
29
37
|
class?: string
|
|
30
38
|
}
|
|
@@ -44,11 +52,37 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
44
52
|
componentProps.initialProperties?.links || []
|
|
45
53
|
)
|
|
46
54
|
|
|
55
|
+
// Bylines state
|
|
56
|
+
const [availableBylines, setAvailableBylines] = createSignal<BylineItem[]>([])
|
|
57
|
+
const [bylines, setBylines] = createSignal<BylineItem[]>(
|
|
58
|
+
componentProps.bylines || componentProps.initialProperties?.bylines || []
|
|
59
|
+
)
|
|
60
|
+
const [isBylineModalOpen, setIsBylineModalOpen] = createSignal(false)
|
|
61
|
+
const [editingBylineIndex, setEditingBylineIndex] = createSignal<number | null>(null)
|
|
62
|
+
const [bylineDraft, setBylineDraft] = createSignal<BylineItem>({
|
|
63
|
+
name: "",
|
|
64
|
+
avatar: "",
|
|
65
|
+
role: "",
|
|
66
|
+
url: ""
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// Taxonomies state
|
|
70
|
+
const [categoriesList, setCategoriesList] = createSignal<
|
|
71
|
+
Array<{ id: string; name: string; slug: string }>
|
|
72
|
+
>([])
|
|
73
|
+
const [selectedCategory, setSelectedCategory] = createSignal<string>(
|
|
74
|
+
componentProps.initialProperties?.category || ""
|
|
75
|
+
)
|
|
76
|
+
const [tagsList, setTagsList] = createSignal<string[]>(
|
|
77
|
+
componentProps.tags || componentProps.initialProperties?.tags || []
|
|
78
|
+
)
|
|
79
|
+
const [newTagInput, setNewTagInput] = createSignal("")
|
|
80
|
+
|
|
47
81
|
const [isLinkModalOpen, setIsLinkModalOpen] = createSignal(false)
|
|
48
82
|
const [editingLinkIndex, setEditingLinkIndex] = createSignal<number | null>(null)
|
|
49
83
|
const [linkDraft, setLinkDraft] = createSignal({ label: "", to: "", icon: "" })
|
|
50
84
|
|
|
51
|
-
onMount(() => {
|
|
85
|
+
onMount(async () => {
|
|
52
86
|
const def = definition()
|
|
53
87
|
if (def?.properties) {
|
|
54
88
|
const initialExpanded: Record<string, boolean> = {}
|
|
@@ -61,9 +95,45 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
61
95
|
const handlePropsChange = (e: Event) => {
|
|
62
96
|
if (e instanceof CustomEvent && e.detail?.properties) {
|
|
63
97
|
setProperties(e.detail.properties)
|
|
98
|
+
if (e.detail.properties.bylines) setBylines(e.detail.properties.bylines)
|
|
99
|
+
if (e.detail.properties.tags) setTagsList(e.detail.properties.tags)
|
|
100
|
+
if (e.detail.properties.category) setSelectedCategory(e.detail.properties.category)
|
|
64
101
|
}
|
|
65
102
|
}
|
|
66
103
|
document.addEventListener("properties-change", handlePropsChange)
|
|
104
|
+
|
|
105
|
+
// Load available taxonomy categories
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetch("/api/cms/taxonomies/category/terms")
|
|
108
|
+
if (res.ok) {
|
|
109
|
+
const data = await res.json()
|
|
110
|
+
if (Array.isArray(data)) {
|
|
111
|
+
setCategoriesList(data.map((t: any) => ({ id: t.id, name: t.name, slug: t.slug })))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// ignore
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Load available global bylines
|
|
119
|
+
try {
|
|
120
|
+
const res = await fetch("/api/cms/bylines")
|
|
121
|
+
if (res.ok) {
|
|
122
|
+
const data = await res.json()
|
|
123
|
+
if (Array.isArray(data)) {
|
|
124
|
+
setAvailableBylines(
|
|
125
|
+
data.map((b: any) => ({
|
|
126
|
+
name: b.name,
|
|
127
|
+
avatar: b.avatar || "",
|
|
128
|
+
role: b.bio || "",
|
|
129
|
+
url: b.websiteUrl || ""
|
|
130
|
+
}))
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// ignore
|
|
136
|
+
}
|
|
67
137
|
})
|
|
68
138
|
|
|
69
139
|
const handlePropertyChange = (key: string, value: any) => {
|
|
@@ -84,6 +154,63 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
84
154
|
setExpandedGroups((prev) => ({ ...prev, [groupId]: !prev[groupId] }))
|
|
85
155
|
}
|
|
86
156
|
|
|
157
|
+
// Bylines Handlers
|
|
158
|
+
const openBylineModal = (index: number | null = null) => {
|
|
159
|
+
setEditingBylineIndex(index)
|
|
160
|
+
if (index !== null && bylines()[index]) {
|
|
161
|
+
const b = bylines()[index]!
|
|
162
|
+
setBylineDraft({ name: b.name, avatar: b.avatar || "", role: b.role || "", url: b.url || "" })
|
|
163
|
+
} else {
|
|
164
|
+
setBylineDraft({ name: "", avatar: "", role: "", url: "" })
|
|
165
|
+
}
|
|
166
|
+
setIsBylineModalOpen(true)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const saveByline = () => {
|
|
170
|
+
const draft = bylineDraft()
|
|
171
|
+
if (!draft.name) return
|
|
172
|
+
const current = [...bylines()]
|
|
173
|
+
const idx = editingBylineIndex()
|
|
174
|
+
if (idx !== null) {
|
|
175
|
+
current[idx] = draft
|
|
176
|
+
} else {
|
|
177
|
+
current.push(draft)
|
|
178
|
+
}
|
|
179
|
+
setBylines(current)
|
|
180
|
+
handlePropertyChange("bylines", current)
|
|
181
|
+
setIsBylineModalOpen(false)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const removeByline = (index: number) => {
|
|
185
|
+
const current = [...bylines()]
|
|
186
|
+
current.splice(index, 1)
|
|
187
|
+
setBylines(current)
|
|
188
|
+
handlePropertyChange("bylines", current)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Taxonomies Handlers
|
|
192
|
+
const handleCategoryChange = (slug: string) => {
|
|
193
|
+
setSelectedCategory(slug)
|
|
194
|
+
handlePropertyChange("category", slug)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const addTag = () => {
|
|
198
|
+
const tag = newTagInput().trim().toLowerCase()
|
|
199
|
+
if (tag && !tagsList().includes(tag)) {
|
|
200
|
+
const updated = [...tagsList(), tag]
|
|
201
|
+
setTagsList(updated)
|
|
202
|
+
handlePropertyChange("tags", updated)
|
|
203
|
+
setNewTagInput("")
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const removeTag = (tagToRemove: string) => {
|
|
208
|
+
const updated = tagsList().filter((t) => t !== tagToRemove)
|
|
209
|
+
setTagsList(updated)
|
|
210
|
+
handlePropertyChange("tags", updated)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Links Handlers
|
|
87
214
|
const openLinkModal = (index: number | null = null) => {
|
|
88
215
|
setEditingLinkIndex(index)
|
|
89
216
|
if (index !== null && links()[index]) {
|
|
@@ -124,9 +251,7 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
124
251
|
if (field.type === "text") {
|
|
125
252
|
return (
|
|
126
253
|
<div class="flex flex-col gap-1 w-full">
|
|
127
|
-
<span class="text-xs font-semibold text-neutral-600
|
|
128
|
-
{label}
|
|
129
|
-
</span>
|
|
254
|
+
<span class="text-xs font-semibold text-neutral-600 block">{label}</span>
|
|
130
255
|
<RLSInput
|
|
131
256
|
value={getTextValue(value)}
|
|
132
257
|
onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
|
|
@@ -142,9 +267,7 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
142
267
|
if (field.type === "number") {
|
|
143
268
|
return (
|
|
144
269
|
<div class="flex flex-col gap-1 w-full">
|
|
145
|
-
<span class="text-xs font-semibold text-neutral-600
|
|
146
|
-
{label}
|
|
147
|
-
</span>
|
|
270
|
+
<span class="text-xs font-semibold text-neutral-600 block">{label}</span>
|
|
148
271
|
<RLSInput
|
|
149
272
|
type="number"
|
|
150
273
|
value={value !== undefined ? String(value) : ""}
|
|
@@ -162,9 +285,7 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
162
285
|
const selectItems = (field.options || []).map((opt) => getTextValue(opt))
|
|
163
286
|
return (
|
|
164
287
|
<div class="flex flex-col gap-1 w-full">
|
|
165
|
-
<span class="text-xs font-semibold text-neutral-600
|
|
166
|
-
{label}
|
|
167
|
-
</span>
|
|
288
|
+
<span class="text-xs font-semibold text-neutral-600 block">{label}</span>
|
|
168
289
|
<RLSSelect
|
|
169
290
|
items={selectItems}
|
|
170
291
|
value={getTextValue(value)}
|
|
@@ -196,9 +317,7 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
196
317
|
|
|
197
318
|
return (
|
|
198
319
|
<div class="flex flex-col gap-1 w-full">
|
|
199
|
-
<span class="text-xs font-semibold text-neutral-600
|
|
200
|
-
{label}
|
|
201
|
-
</span>
|
|
320
|
+
<span class="text-xs font-semibold text-neutral-600 block">{label}</span>
|
|
202
321
|
<div class="flex flex-wrap gap-1 mb-1">
|
|
203
322
|
<For each={arr}>
|
|
204
323
|
{(item, idx) => (
|
|
@@ -207,7 +326,7 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
207
326
|
<button
|
|
208
327
|
type="button"
|
|
209
328
|
onClick={() => removeArrayItem(idx())}
|
|
210
|
-
class="ml-1 text-muted
|
|
329
|
+
class="ml-1 text-muted hover:text-error-500 cursor-pointer bg-transparent border-0 p-0 inline-flex items-center"
|
|
211
330
|
>
|
|
212
331
|
×
|
|
213
332
|
</button>
|
|
@@ -253,16 +372,168 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
253
372
|
{/* Top header content */}
|
|
254
373
|
<div class="flex flex-col gap-2 items-center text-center">
|
|
255
374
|
<div class="relative group/icon mb-1">
|
|
256
|
-
<div class="w-10 h-10 rounded-full flex items-center justify-center border border-default text-muted
|
|
375
|
+
<div class="w-10 h-10 rounded-full flex items-center justify-center border border-default text-muted">
|
|
257
376
|
<RLSIcon name="fileText" size="md" />
|
|
258
377
|
</div>
|
|
259
378
|
</div>
|
|
260
379
|
|
|
261
|
-
<span class="text-xs
|
|
380
|
+
<span class="text-xs uppercase tracking-wider text-center block font-semibold text-muted">
|
|
262
381
|
{pageType()} Properties
|
|
263
382
|
</span>
|
|
264
383
|
</div>
|
|
265
384
|
|
|
385
|
+
{/* Taxonomies Section */}
|
|
386
|
+
<div class="mt-4 p-3 rounded-lg border border-default/70 bg-elevated/30 flex flex-col gap-3">
|
|
387
|
+
<h6 class="text-xs font-bold uppercase tracking-wider text-muted">Taxonomies</h6>
|
|
388
|
+
|
|
389
|
+
{/* Category */}
|
|
390
|
+
<div class="flex flex-col gap-1 w-full">
|
|
391
|
+
<span class="text-xs font-semibold text-neutral-600 block">Category</span>
|
|
392
|
+
<select
|
|
393
|
+
value={selectedCategory()}
|
|
394
|
+
onChange={(e) => handleCategoryChange(e.currentTarget.value)}
|
|
395
|
+
class="w-full text-xs py-1.5 px-2.5 rounded-md border border-default bg-background text-highlighted focus:outline-none focus:ring-1 focus:ring-primary"
|
|
396
|
+
>
|
|
397
|
+
<option value="">(None)</option>
|
|
398
|
+
<For each={categoriesList()}>
|
|
399
|
+
{(cat) => (
|
|
400
|
+
<option value={cat.slug} selected={selectedCategory() === cat.slug}>
|
|
401
|
+
{cat.name}
|
|
402
|
+
</option>
|
|
403
|
+
)}
|
|
404
|
+
</For>
|
|
405
|
+
</select>
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
{/* Tags */}
|
|
409
|
+
<div class="flex flex-col gap-1 w-full">
|
|
410
|
+
<span class="text-xs font-semibold text-neutral-600 block">Tags</span>
|
|
411
|
+
<div class="flex flex-wrap gap-1 mb-1">
|
|
412
|
+
<For each={tagsList()}>
|
|
413
|
+
{(tag) => (
|
|
414
|
+
<RLSBadge variant="soft" color="primary" size="sm">
|
|
415
|
+
<span>#{tag}</span>
|
|
416
|
+
<button
|
|
417
|
+
type="button"
|
|
418
|
+
onClick={() => removeTag(tag)}
|
|
419
|
+
class="ml-1 text-muted hover:text-error-500 cursor-pointer bg-transparent border-0 p-0 inline-flex items-center"
|
|
420
|
+
>
|
|
421
|
+
×
|
|
422
|
+
</button>
|
|
423
|
+
</RLSBadge>
|
|
424
|
+
)}
|
|
425
|
+
</For>
|
|
426
|
+
</div>
|
|
427
|
+
<div class="flex gap-1 mt-1">
|
|
428
|
+
<div class="flex-1">
|
|
429
|
+
<RLSInput
|
|
430
|
+
value={newTagInput()}
|
|
431
|
+
onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
|
|
432
|
+
setNewTagInput(e.currentTarget.value)
|
|
433
|
+
}
|
|
434
|
+
onKeyDown={(e: KeyboardEvent) => {
|
|
435
|
+
if (e.key === "Enter") {
|
|
436
|
+
e.preventDefault()
|
|
437
|
+
addTag()
|
|
438
|
+
}
|
|
439
|
+
}}
|
|
440
|
+
size="sm"
|
|
441
|
+
placeholder="Add tag..."
|
|
442
|
+
/>
|
|
443
|
+
</div>
|
|
444
|
+
<RLSButton variant="outline" color="neutral" size="sm" onClick={addTag} label="+" />
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
|
|
449
|
+
{/* Bylines / Authors Section */}
|
|
450
|
+
<div class="mt-3 p-3 rounded-lg border border-default/70 bg-elevated/30 flex flex-col gap-2">
|
|
451
|
+
<div class="flex items-center justify-between mb-1">
|
|
452
|
+
<h6 class="text-xs font-bold uppercase tracking-wider text-muted">Bylines / Authors</h6>
|
|
453
|
+
<RLSButton
|
|
454
|
+
variant="ghost"
|
|
455
|
+
color="neutral"
|
|
456
|
+
size="xs"
|
|
457
|
+
onClick={() => openBylineModal()}
|
|
458
|
+
leadingIcon="plus"
|
|
459
|
+
label="Add Custom"
|
|
460
|
+
/>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
<Show when={availableBylines().length > 0}>
|
|
464
|
+
<select
|
|
465
|
+
onChange={(e) => {
|
|
466
|
+
const authorName = e.currentTarget.value
|
|
467
|
+
if (!authorName) return
|
|
468
|
+
const found = availableBylines().find((b) => b.name === authorName)
|
|
469
|
+
if (found && !bylines().some((b) => b.name === found.name)) {
|
|
470
|
+
const updated = [...bylines(), found]
|
|
471
|
+
setBylines(updated)
|
|
472
|
+
handlePropertyChange("bylines", updated)
|
|
473
|
+
}
|
|
474
|
+
e.currentTarget.value = ""
|
|
475
|
+
}}
|
|
476
|
+
class="w-full text-xs py-1.5 px-2 rounded-md border border-default bg-background text-highlighted focus:outline-none focus:ring-1 focus:ring-primary mb-1"
|
|
477
|
+
>
|
|
478
|
+
<option value="">+ Select from global bylines...</option>
|
|
479
|
+
<For each={availableBylines()}>
|
|
480
|
+
{(author) => <option value={author.name}>{author.name}</option>}
|
|
481
|
+
</For>
|
|
482
|
+
</select>
|
|
483
|
+
</Show>
|
|
484
|
+
|
|
485
|
+
<Show
|
|
486
|
+
when={bylines().length > 0}
|
|
487
|
+
fallback={<p class="text-xs text-muted italic">No authors attached</p>}
|
|
488
|
+
>
|
|
489
|
+
<div class="flex flex-col gap-2">
|
|
490
|
+
<For each={bylines()}>
|
|
491
|
+
{(byline, idx) => (
|
|
492
|
+
<div class="flex items-center justify-between p-2 rounded-lg border border-default/50 text-xs bg-background/50">
|
|
493
|
+
<div class="flex items-center gap-2 truncate">
|
|
494
|
+
{byline.avatar ? (
|
|
495
|
+
<img
|
|
496
|
+
src={byline.avatar}
|
|
497
|
+
alt={byline.name}
|
|
498
|
+
class="w-6 h-6 rounded-full object-cover shrink-0"
|
|
499
|
+
/>
|
|
500
|
+
) : (
|
|
501
|
+
<div class="w-6 h-6 rounded-full bg-primary/10 text-primary flex items-center justify-center font-bold text-[10px] shrink-0">
|
|
502
|
+
{byline.name.charAt(0).toUpperCase()}
|
|
503
|
+
</div>
|
|
504
|
+
)}
|
|
505
|
+
<div class="flex flex-col truncate">
|
|
506
|
+
<span class="font-semibold text-highlighted truncate">{byline.name}</span>
|
|
507
|
+
{byline.role && (
|
|
508
|
+
<span class="text-[10px] text-muted truncate">{byline.role}</span>
|
|
509
|
+
)}
|
|
510
|
+
</div>
|
|
511
|
+
</div>
|
|
512
|
+
<div class="flex items-center gap-1 shrink-0">
|
|
513
|
+
<button
|
|
514
|
+
type="button"
|
|
515
|
+
onClick={() => openBylineModal(idx())}
|
|
516
|
+
class="text-muted hover:text-primary cursor-pointer p-1"
|
|
517
|
+
title="Edit"
|
|
518
|
+
>
|
|
519
|
+
<RLSIcon name="edit" size="xs" />
|
|
520
|
+
</button>
|
|
521
|
+
<button
|
|
522
|
+
type="button"
|
|
523
|
+
onClick={() => removeByline(idx())}
|
|
524
|
+
class="text-muted hover:text-error-500 cursor-pointer p-1"
|
|
525
|
+
title="Delete"
|
|
526
|
+
>
|
|
527
|
+
<RLSIcon name="trash" size="xs" />
|
|
528
|
+
</button>
|
|
529
|
+
</div>
|
|
530
|
+
</div>
|
|
531
|
+
)}
|
|
532
|
+
</For>
|
|
533
|
+
</div>
|
|
534
|
+
</Show>
|
|
535
|
+
</div>
|
|
536
|
+
|
|
266
537
|
{/* Property Groups Accordion */}
|
|
267
538
|
<div class="mt-4 space-y-2 w-full">
|
|
268
539
|
<For each={Object.entries(definition()?.properties || {})}>
|
|
@@ -273,13 +544,13 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
273
544
|
<button
|
|
274
545
|
type="button"
|
|
275
546
|
onClick={() => toggleGroup(groupId)}
|
|
276
|
-
class="flex justify-between items-center w-full p-2 rounded-lg text-left font-medium text-sm text-
|
|
547
|
+
class="flex justify-between items-center w-full p-2 rounded-lg text-left font-medium text-sm text-highlighted hover:bg-elevated transition cursor-pointer mt-2 bg-transparent border-0"
|
|
277
548
|
>
|
|
278
549
|
<span>{getTextValue(group.label)}</span>
|
|
279
550
|
<RLSIcon
|
|
280
551
|
name="chevronDown"
|
|
281
552
|
size="sm"
|
|
282
|
-
class={`transition-transform duration-150 text-muted
|
|
553
|
+
class={`transition-transform duration-150 text-muted ${
|
|
283
554
|
isExpanded() ? "rotate-180" : ""
|
|
284
555
|
}`}
|
|
285
556
|
/>
|
|
@@ -302,7 +573,7 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
302
573
|
{/* Links section */}
|
|
303
574
|
<div class="flex flex-col gap-2 pt-2 border-t border-default">
|
|
304
575
|
<div class="flex items-center justify-between mb-1">
|
|
305
|
-
<h6 class="text-xs font-bold uppercase tracking-wider text-muted
|
|
576
|
+
<h6 class="text-xs font-bold uppercase tracking-wider text-muted">Links</h6>
|
|
306
577
|
<RLSButton
|
|
307
578
|
variant="ghost"
|
|
308
579
|
color="neutral"
|
|
@@ -315,15 +586,15 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
315
586
|
|
|
316
587
|
<Show
|
|
317
588
|
when={links().length > 0}
|
|
318
|
-
fallback={<p class="text-xs text-muted
|
|
589
|
+
fallback={<p class="text-xs text-muted italic">No links added</p>}
|
|
319
590
|
>
|
|
320
591
|
<div class="flex flex-col gap-2">
|
|
321
592
|
<For each={links()}>
|
|
322
593
|
{(item, index) => (
|
|
323
594
|
<div class="flex items-center justify-between p-2 rounded-lg border border-default text-sm">
|
|
324
595
|
<div class="flex items-center gap-2 truncate">
|
|
325
|
-
<RLSIcon name="link" size="xs" class="text-muted
|
|
326
|
-
<span class="font-medium text-
|
|
596
|
+
<RLSIcon name="link" size="xs" class="text-muted shrink-0" />
|
|
597
|
+
<span class="font-medium text-highlighted truncate">{item.label}</span>
|
|
327
598
|
</div>
|
|
328
599
|
<div class="flex items-center gap-1 shrink-0">
|
|
329
600
|
<RLSButton
|
|
@@ -344,7 +615,7 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
344
615
|
onClick={() => removeLink(index())}
|
|
345
616
|
aria-label="Delete"
|
|
346
617
|
title="Delete"
|
|
347
|
-
class="!text-
|
|
618
|
+
class="!text-error-500 hover:!text-error-600"
|
|
348
619
|
leading={<RLSIcon name="trash" size="xs" />}
|
|
349
620
|
/>
|
|
350
621
|
</div>
|
|
@@ -355,6 +626,77 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
355
626
|
</Show>
|
|
356
627
|
</div>
|
|
357
628
|
|
|
629
|
+
{/* Byline Modal */}
|
|
630
|
+
<RLSModal
|
|
631
|
+
open={isBylineModalOpen()}
|
|
632
|
+
onClose={() => setIsBylineModalOpen(false)}
|
|
633
|
+
title={editingBylineIndex() !== null ? "Edit Author Byline" : "Add Author Byline"}
|
|
634
|
+
footerSlot={
|
|
635
|
+
<>
|
|
636
|
+
<RLSButton
|
|
637
|
+
variant="outline"
|
|
638
|
+
color="neutral"
|
|
639
|
+
size="sm"
|
|
640
|
+
onClick={() => setIsBylineModalOpen(false)}
|
|
641
|
+
label="Cancel"
|
|
642
|
+
/>
|
|
643
|
+
<RLSButton
|
|
644
|
+
variant="solid"
|
|
645
|
+
color="primary"
|
|
646
|
+
size="sm"
|
|
647
|
+
onClick={saveByline}
|
|
648
|
+
label={editingBylineIndex() !== null ? "Update" : "Add"}
|
|
649
|
+
/>
|
|
650
|
+
</>
|
|
651
|
+
}
|
|
652
|
+
>
|
|
653
|
+
<div class="flex flex-col gap-4 py-2">
|
|
654
|
+
<div class="flex flex-col gap-1">
|
|
655
|
+
<span class="text-xs font-semibold text-muted">Author Name *</span>
|
|
656
|
+
<RLSInput
|
|
657
|
+
value={bylineDraft().name}
|
|
658
|
+
onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
|
|
659
|
+
setBylineDraft({ ...bylineDraft(), name: e.currentTarget.value })
|
|
660
|
+
}
|
|
661
|
+
placeholder="e.g. Idan"
|
|
662
|
+
/>
|
|
663
|
+
</div>
|
|
664
|
+
|
|
665
|
+
<div class="flex flex-col gap-1">
|
|
666
|
+
<span class="text-xs font-semibold text-muted">Role / Title</span>
|
|
667
|
+
<RLSInput
|
|
668
|
+
value={bylineDraft().role || ""}
|
|
669
|
+
onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
|
|
670
|
+
setBylineDraft({ ...bylineDraft(), role: e.currentTarget.value })
|
|
671
|
+
}
|
|
672
|
+
placeholder="e.g. Lead Producer"
|
|
673
|
+
/>
|
|
674
|
+
</div>
|
|
675
|
+
|
|
676
|
+
<div class="flex flex-col gap-1">
|
|
677
|
+
<span class="text-xs font-semibold text-muted">Avatar URL</span>
|
|
678
|
+
<RLSInput
|
|
679
|
+
value={bylineDraft().avatar || ""}
|
|
680
|
+
onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
|
|
681
|
+
setBylineDraft({ ...bylineDraft(), avatar: e.currentTarget.value })
|
|
682
|
+
}
|
|
683
|
+
placeholder="https://..."
|
|
684
|
+
/>
|
|
685
|
+
</div>
|
|
686
|
+
|
|
687
|
+
<div class="flex flex-col gap-1">
|
|
688
|
+
<span class="text-xs font-semibold text-muted">Profile / Website URL</span>
|
|
689
|
+
<RLSInput
|
|
690
|
+
value={bylineDraft().url || ""}
|
|
691
|
+
onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
|
|
692
|
+
setBylineDraft({ ...bylineDraft(), url: e.currentTarget.value })
|
|
693
|
+
}
|
|
694
|
+
placeholder="https://rimelight.com/authors/..."
|
|
695
|
+
/>
|
|
696
|
+
</div>
|
|
697
|
+
</div>
|
|
698
|
+
</RLSModal>
|
|
699
|
+
|
|
358
700
|
{/* Link Add/Edit Modal */}
|
|
359
701
|
<RLSModal
|
|
360
702
|
open={isLinkModalOpen()}
|
|
@@ -381,7 +723,7 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
381
723
|
>
|
|
382
724
|
<div class="flex flex-col gap-4 py-2">
|
|
383
725
|
<div class="flex flex-col gap-1">
|
|
384
|
-
<span class="text-xs font-semibold text-muted
|
|
726
|
+
<span class="text-xs font-semibold text-muted">Label</span>
|
|
385
727
|
<RLSInput
|
|
386
728
|
value={linkDraft().label}
|
|
387
729
|
onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
|
|
@@ -392,7 +734,7 @@ export const RimelightPropertiesPanel: Component<RimelightPropertiesPanelProps>
|
|
|
392
734
|
</div>
|
|
393
735
|
|
|
394
736
|
<div class="flex flex-col gap-1">
|
|
395
|
-
<span class="text-xs font-semibold text-muted
|
|
737
|
+
<span class="text-xs font-semibold text-muted">URL</span>
|
|
396
738
|
<RLSInput
|
|
397
739
|
value={linkDraft().to}
|
|
398
740
|
onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) =>
|