@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
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
export interface StorageFile {
|
|
2
|
+
key: string
|
|
3
|
+
name: string
|
|
4
|
+
size: number
|
|
5
|
+
mimeType: string
|
|
6
|
+
uploadedAt: string
|
|
7
|
+
url: string
|
|
8
|
+
etag?: string
|
|
9
|
+
customMetadata?: Record<string, string>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface StorageListResult {
|
|
13
|
+
files: StorageFile[]
|
|
14
|
+
truncated: boolean
|
|
15
|
+
cursor?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface R2StorageOptions {
|
|
19
|
+
binding?: string | undefined
|
|
20
|
+
publicUrl?: string | undefined
|
|
21
|
+
prefix?: string | undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface StorageConfig {
|
|
25
|
+
provider: "r2" | "local" | "s3"
|
|
26
|
+
binding?: string | undefined
|
|
27
|
+
publicUrl?: string | undefined
|
|
28
|
+
prefix?: string | undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface StorageListOptions {
|
|
32
|
+
prefix?: string | undefined
|
|
33
|
+
cursor?: string | undefined
|
|
34
|
+
limit?: number | undefined
|
|
35
|
+
publicUrl?: string | undefined
|
|
36
|
+
apiBasePath?: string | undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface StorageUploadOptions {
|
|
40
|
+
key?: string | undefined
|
|
41
|
+
prefix?: string | undefined
|
|
42
|
+
publicUrl?: string | undefined
|
|
43
|
+
apiBasePath?: string | undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function r2(options: R2StorageOptions = {}): StorageConfig {
|
|
47
|
+
return {
|
|
48
|
+
provider: "r2",
|
|
49
|
+
binding: options.binding ?? "BLOB",
|
|
50
|
+
publicUrl: options.publicUrl,
|
|
51
|
+
prefix: options.prefix ?? "media/"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolves Cloudflare R2 bucket instance from Cloudflare env or Astro locals.
|
|
57
|
+
*/
|
|
58
|
+
export async function getR2Bucket(locals?: any, bindingName = "BLOB"): Promise<any> {
|
|
59
|
+
// 1. Try cloudflare:workers standard export (Astro v6+)
|
|
60
|
+
try {
|
|
61
|
+
// @ts-ignore
|
|
62
|
+
const cf = await import("cloudflare:workers")
|
|
63
|
+
if (cf?.env && cf.env[bindingName]) {
|
|
64
|
+
return cf.env[bindingName]
|
|
65
|
+
}
|
|
66
|
+
} catch {}
|
|
67
|
+
|
|
68
|
+
// 2. Direct locals property check (locals.env or locals[bindingName])
|
|
69
|
+
if (locals) {
|
|
70
|
+
if (locals.env && locals.env[bindingName]) {
|
|
71
|
+
return locals.env[bindingName]
|
|
72
|
+
}
|
|
73
|
+
if (locals[bindingName]) {
|
|
74
|
+
return locals[bindingName]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 3. Global process.env fallback
|
|
79
|
+
if (typeof process !== "undefined" && process.env && (process.env as any)[bindingName]) {
|
|
80
|
+
return (process.env as any)[bindingName]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Lists files from R2 Bucket under configured prefix.
|
|
88
|
+
*/
|
|
89
|
+
export async function listR2Files(
|
|
90
|
+
bucket: any,
|
|
91
|
+
options: StorageListOptions = {}
|
|
92
|
+
): Promise<StorageListResult> {
|
|
93
|
+
if (!bucket || typeof bucket.list !== "function") {
|
|
94
|
+
return { files: [], truncated: false }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const prefix = options.prefix ?? "media/"
|
|
98
|
+
const limit = options.limit ?? 50
|
|
99
|
+
|
|
100
|
+
const result = await bucket.list({
|
|
101
|
+
prefix,
|
|
102
|
+
cursor: options.cursor,
|
|
103
|
+
limit
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const files: StorageFile[] = (result.objects || []).map((obj: any) => {
|
|
107
|
+
const rawKey = obj.key as string
|
|
108
|
+
const name = rawKey.startsWith(prefix) ? rawKey.slice(prefix.length) : rawKey
|
|
109
|
+
const cleanedName = name.includes("/") ? name.split("/").pop()! : name
|
|
110
|
+
const mimeType = obj.httpMetadata?.contentType || guessMimeType(rawKey)
|
|
111
|
+
|
|
112
|
+
// Resolve public URL: custom domain, or proxy endpoint
|
|
113
|
+
let url = ""
|
|
114
|
+
if (options.publicUrl) {
|
|
115
|
+
const baseUrl = options.publicUrl.endsWith("/")
|
|
116
|
+
? options.publicUrl.slice(0, -1)
|
|
117
|
+
: options.publicUrl
|
|
118
|
+
url = `${baseUrl}/${rawKey}`
|
|
119
|
+
} else if (options.apiBasePath) {
|
|
120
|
+
const baseApi = options.apiBasePath.endsWith("/")
|
|
121
|
+
? options.apiBasePath.slice(0, -1)
|
|
122
|
+
: options.apiBasePath
|
|
123
|
+
url = `${baseApi}/${rawKey}`
|
|
124
|
+
} else {
|
|
125
|
+
url = `/cms/api/media/${rawKey}`
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
key: rawKey,
|
|
130
|
+
name: cleanedName,
|
|
131
|
+
size: obj.size,
|
|
132
|
+
mimeType,
|
|
133
|
+
uploadedAt: obj.uploaded ? new Date(obj.uploaded).toISOString() : new Date().toISOString(),
|
|
134
|
+
url,
|
|
135
|
+
etag: obj.etag,
|
|
136
|
+
customMetadata: obj.customMetadata
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
files,
|
|
142
|
+
truncated: result.truncated,
|
|
143
|
+
cursor: result.cursor
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Uploads a file buffer/stream into R2 Bucket.
|
|
149
|
+
*/
|
|
150
|
+
export async function uploadR2File(
|
|
151
|
+
bucket: any,
|
|
152
|
+
file: File,
|
|
153
|
+
options: StorageUploadOptions = {}
|
|
154
|
+
): Promise<StorageFile> {
|
|
155
|
+
if (!bucket || typeof bucket.put !== "function") {
|
|
156
|
+
throw new Error("R2 bucket binding is not available in runtime environment.")
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const prefix = options.prefix ?? "media/"
|
|
160
|
+
const timestamp = Date.now()
|
|
161
|
+
const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, "_")
|
|
162
|
+
const randomSuffix = Math.random().toString(36).substring(2, 8)
|
|
163
|
+
const key = options.key ?? `${prefix}${timestamp}-${randomSuffix}/${safeName}`
|
|
164
|
+
|
|
165
|
+
const arrayBuffer = await file.arrayBuffer()
|
|
166
|
+
|
|
167
|
+
const r2Object = await bucket.put(key, arrayBuffer, {
|
|
168
|
+
httpMetadata: {
|
|
169
|
+
contentType: file.type || guessMimeType(safeName),
|
|
170
|
+
contentDisposition: `inline; filename="${safeName}"`
|
|
171
|
+
},
|
|
172
|
+
customMetadata: {
|
|
173
|
+
originalName: file.name,
|
|
174
|
+
uploadedAt: new Date().toISOString()
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
let url = ""
|
|
179
|
+
if (options.publicUrl) {
|
|
180
|
+
const baseUrl = options.publicUrl.endsWith("/")
|
|
181
|
+
? options.publicUrl.slice(0, -1)
|
|
182
|
+
: options.publicUrl
|
|
183
|
+
url = `${baseUrl}/${key}`
|
|
184
|
+
} else if (options.apiBasePath) {
|
|
185
|
+
const baseApi = options.apiBasePath.endsWith("/")
|
|
186
|
+
? options.apiBasePath.slice(0, -1)
|
|
187
|
+
: options.apiBasePath
|
|
188
|
+
url = `${baseApi}/${key}`
|
|
189
|
+
} else {
|
|
190
|
+
url = `/cms/api/media/${key}`
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
key,
|
|
195
|
+
name: safeName,
|
|
196
|
+
size: file.size,
|
|
197
|
+
mimeType: file.type || guessMimeType(safeName),
|
|
198
|
+
uploadedAt: new Date().toISOString(),
|
|
199
|
+
url,
|
|
200
|
+
etag: r2Object?.etag
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Deletes a file from R2 Bucket by key.
|
|
206
|
+
*/
|
|
207
|
+
export async function deleteR2File(bucket: any, key: string): Promise<boolean> {
|
|
208
|
+
if (!bucket || typeof bucket.delete !== "function") {
|
|
209
|
+
throw new Error("R2 bucket binding is not available in runtime environment.")
|
|
210
|
+
}
|
|
211
|
+
await bucket.delete(key)
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* MIME type guesser fallback.
|
|
217
|
+
*/
|
|
218
|
+
function guessMimeType(filename: string): string {
|
|
219
|
+
const ext = filename.split(".").pop()?.toLowerCase()
|
|
220
|
+
switch (ext) {
|
|
221
|
+
case "jpg":
|
|
222
|
+
case "jpeg":
|
|
223
|
+
return "image/jpeg"
|
|
224
|
+
case "png":
|
|
225
|
+
return "image/png"
|
|
226
|
+
case "webp":
|
|
227
|
+
return "image/webp"
|
|
228
|
+
case "gif":
|
|
229
|
+
return "image/gif"
|
|
230
|
+
case "svg":
|
|
231
|
+
return "image/svg+xml"
|
|
232
|
+
case "avif":
|
|
233
|
+
return "image/avif"
|
|
234
|
+
case "mp4":
|
|
235
|
+
return "video/mp4"
|
|
236
|
+
case "webm":
|
|
237
|
+
return "video/webm"
|
|
238
|
+
case "mp3":
|
|
239
|
+
return "audio/mpeg"
|
|
240
|
+
case "wav":
|
|
241
|
+
return "audio/wav"
|
|
242
|
+
case "pdf":
|
|
243
|
+
return "application/pdf"
|
|
244
|
+
case "json":
|
|
245
|
+
return "application/json"
|
|
246
|
+
case "txt":
|
|
247
|
+
return "text/plain"
|
|
248
|
+
case "zip":
|
|
249
|
+
return "application/zip"
|
|
250
|
+
default:
|
|
251
|
+
return "application/octet-stream"
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import type { BaseBlock } from "@rimelight/cms"
|
|
3
|
-
function filterProps(props: any[], options: { hide?: string[]; show?: string[] }): any[] {
|
|
4
|
-
if (options.show?.length) return props.filter((p: any) => options.show!.includes(p.name))
|
|
5
|
-
if (options.hide?.length) return props.filter((p: any) => !options.hide!.includes(p.name))
|
|
6
|
-
return props
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
block: BaseBlock
|
|
11
|
-
locale?: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const { block } = Astro.props
|
|
15
|
-
const { componentName, sections = {}, hide = [], show = [] } = block.props
|
|
16
|
-
const compMeta = block.props.meta
|
|
17
|
-
const themeCode = block.props.themeCode
|
|
18
|
-
|
|
19
|
-
const filterOpts: { hide?: string[]; show?: string[] } = { hide, show }
|
|
20
|
-
const props = filterProps(compMeta?.props ?? [], filterOpts)
|
|
21
|
-
const slots = compMeta?.slots ?? []
|
|
22
|
-
const emits = compMeta?.emits ?? []
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
{(sections.props !== false) && (
|
|
26
|
-
<div class="api-documentation-section mb-6">
|
|
27
|
-
<h3 class="text-lg font-semibold text-foreground mb-3">Props</h3>
|
|
28
|
-
{props.length > 0 ? (
|
|
29
|
-
<div class="overflow-x-auto">
|
|
30
|
-
<table class="w-full text-sm border-collapse">
|
|
31
|
-
<thead>
|
|
32
|
-
<tr class="border-b border-border">
|
|
33
|
-
<th class="text-left py-2 px-3 font-semibold">Name</th>
|
|
34
|
-
<th class="text-left py-2 px-3 font-semibold">Type</th>
|
|
35
|
-
<th class="text-left py-2 px-3 font-semibold">Required</th>
|
|
36
|
-
<th class="text-left py-2 px-3 font-semibold">Default</th>
|
|
37
|
-
<th class="text-left py-2 px-3 font-semibold">Description</th>
|
|
38
|
-
</tr>
|
|
39
|
-
</thead>
|
|
40
|
-
<tbody>
|
|
41
|
-
{props.map((prop: any) => (
|
|
42
|
-
<tr class="border-b border-border/50">
|
|
43
|
-
<td><span class="font-mono text-sm">{prop.name}</span></td>
|
|
44
|
-
<td><span class="font-mono text-xs text-muted-foreground">{prop.type}</span></td>
|
|
45
|
-
<td>{prop.required ? "Yes" : "No"}</td>
|
|
46
|
-
<td>{prop.default !== undefined ? <span class="font-mono text-xs text-muted-foreground">{prop.default}</span> : "—"}</td>
|
|
47
|
-
<td>{prop.description || "—"}</td>
|
|
48
|
-
</tr>
|
|
49
|
-
))}
|
|
50
|
-
</tbody>
|
|
51
|
-
</table>
|
|
52
|
-
</div>
|
|
53
|
-
) : (
|
|
54
|
-
<p class="text-sm italic text-muted-foreground">No props to display.</p>
|
|
55
|
-
)}
|
|
56
|
-
</div>
|
|
57
|
-
)}
|
|
58
|
-
|
|
59
|
-
{(sections.slots !== false) && (
|
|
60
|
-
<div class="api-documentation-section mb-6">
|
|
61
|
-
<h3 class="text-lg font-semibold text-foreground mb-3">Slots</h3>
|
|
62
|
-
{slots.length === 0 ? (
|
|
63
|
-
<p class="text-sm italic text-muted-foreground">No slots to display.</p>
|
|
64
|
-
) : (
|
|
65
|
-
<div class="overflow-x-auto">
|
|
66
|
-
<table class="w-full text-sm border-collapse">
|
|
67
|
-
<thead>
|
|
68
|
-
<tr class="border-b border-border">
|
|
69
|
-
<th class="text-left py-2 px-3 font-semibold">Slot</th>
|
|
70
|
-
<th class="text-left py-2 px-3 font-semibold">Type</th>
|
|
71
|
-
</tr>
|
|
72
|
-
</thead>
|
|
73
|
-
<tbody>
|
|
74
|
-
{slots.map((slot: any) => (
|
|
75
|
-
<tr class="border-b border-border/50">
|
|
76
|
-
<td class="py-2 px-3">
|
|
77
|
-
<span class="font-mono text-sm">{slot.name}</span>
|
|
78
|
-
</td>
|
|
79
|
-
<td class="py-2 px-3">
|
|
80
|
-
{Object.keys(slot.bindings || {}).length > 0 ? (
|
|
81
|
-
<span class="font-mono text-xs text-muted-foreground">
|
|
82
|
-
{Object.values(slot.bindings)[0]}
|
|
83
|
-
</span>
|
|
84
|
-
) : (
|
|
85
|
-
<span class="text-muted-foreground">-</span>
|
|
86
|
-
)}
|
|
87
|
-
</td>
|
|
88
|
-
</tr>
|
|
89
|
-
))}
|
|
90
|
-
</tbody>
|
|
91
|
-
</table>
|
|
92
|
-
</div>
|
|
93
|
-
)}
|
|
94
|
-
</div>
|
|
95
|
-
)}
|
|
96
|
-
|
|
97
|
-
{(sections.emits !== false) && (
|
|
98
|
-
<div class="api-documentation-section mb-6">
|
|
99
|
-
<h3 class="text-lg font-semibold text-foreground mb-3">Emits</h3>
|
|
100
|
-
{emits.length === 0 ? (
|
|
101
|
-
<p class="text-sm italic text-muted-foreground">No events emitted by this component.</p>
|
|
102
|
-
) : (
|
|
103
|
-
<div class="overflow-x-auto">
|
|
104
|
-
<table class="w-full text-sm border-collapse">
|
|
105
|
-
<thead>
|
|
106
|
-
<tr class="border-b border-border">
|
|
107
|
-
<th class="text-left py-2 px-3 font-semibold">Event</th>
|
|
108
|
-
<th class="text-left py-2 px-3 font-semibold">Payload</th>
|
|
109
|
-
</tr>
|
|
110
|
-
</thead>
|
|
111
|
-
<tbody>
|
|
112
|
-
{emits.map((emit: any) => (
|
|
113
|
-
<tr class="border-b border-border/50">
|
|
114
|
-
<td class="py-2 px-3">
|
|
115
|
-
<span class="font-mono text-sm">{emit.name}</span>
|
|
116
|
-
</td>
|
|
117
|
-
<td class="py-2 px-3">
|
|
118
|
-
<span class="font-mono text-xs text-muted-foreground">{emit.type}</span>
|
|
119
|
-
</td>
|
|
120
|
-
</tr>
|
|
121
|
-
))}
|
|
122
|
-
</tbody>
|
|
123
|
-
</table>
|
|
124
|
-
</div>
|
|
125
|
-
)}
|
|
126
|
-
</div>
|
|
127
|
-
)}
|
|
128
|
-
|
|
129
|
-
{(sections.theme !== false) && (
|
|
130
|
-
<div class="api-documentation-section mb-6">
|
|
131
|
-
<h3 class="text-lg font-semibold text-foreground mb-3">Theme</h3>
|
|
132
|
-
{themeCode ? (
|
|
133
|
-
<div class="api-theme-container relative border border-border rounded-lg overflow-hidden group mb-6" data-component={componentName}>
|
|
134
|
-
<div class="api-theme-collapse overflow-hidden max-h-[240px] relative transition-[max-height] duration-300 ease-in-out">
|
|
135
|
-
<div class="expressive-code m-0 border-0 rounded-none">
|
|
136
|
-
<figure class="frame m-0 border-0">
|
|
137
|
-
<pre class="language-ts overflow-x-auto m-0 border-0 rounded-none bg-muted"><code class="language-ts m-0">{themeCode}</code></pre>
|
|
138
|
-
</figure>
|
|
139
|
-
</div>
|
|
140
|
-
<div class="api-theme-fade absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-t from-background to-transparent pointer-events-none transition-opacity duration-300"></div>
|
|
141
|
-
</div>
|
|
142
|
-
<div class="api-theme-trigger-bar absolute bottom-0 left-0 right-0 flex justify-center pb-3 bg-gradient-to-t from-background/90 via-background/70 to-transparent pt-12">
|
|
143
|
-
<button class="api-theme-btn px-4 py-1.5 text-xs font-semibold text-primary-foreground bg-primary hover:bg-primary/90 border border-border rounded-md shadow-md transition-all duration-200 cursor-pointer">Show theme config</button>
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
) : (
|
|
147
|
-
<p class="text-sm italic text-muted-foreground">No theme data available.</p>
|
|
148
|
-
)}
|
|
149
|
-
</div>
|
|
150
|
-
)}
|
|
151
|
-
|
|
152
|
-
{(sections.changelog) && (
|
|
153
|
-
<div class="api-documentation-section mb-6">
|
|
154
|
-
<h3 class="text-lg font-semibold text-foreground mb-3">Changelog</h3>
|
|
155
|
-
<div class="api-changelog-container group" data-component={componentName}>
|
|
156
|
-
<div class="api-changelog-loading py-6 flex flex-col gap-4">
|
|
157
|
-
<div class="animate-pulse h-4 bg-muted rounded w-1/4"></div>
|
|
158
|
-
<div class="animate-pulse h-3 bg-muted rounded w-full"></div>
|
|
159
|
-
<div class="animate-pulse h-3 bg-muted rounded w-5/6"></div>
|
|
160
|
-
</div>
|
|
161
|
-
<div class="api-changelog-content hidden">
|
|
162
|
-
<div class="api-changelog-timeline-target"></div>
|
|
163
|
-
</div>
|
|
164
|
-
<div class="api-changelog-empty hidden text-sm italic text-muted-foreground py-2">
|
|
165
|
-
No recent changes found.
|
|
166
|
-
</div>
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
)}
|
|
170
|
-
|
|
171
|
-
<script>
|
|
172
|
-
function escapeHtml(str: string): string {
|
|
173
|
-
return str
|
|
174
|
-
.replace(/&/g, "&")
|
|
175
|
-
.replace(/</g, "<")
|
|
176
|
-
.replace(/>/g, ">")
|
|
177
|
-
.replace(/"/g, """)
|
|
178
|
-
.replace(/'/g, "'");
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async function loadChangelog() {
|
|
182
|
-
if (typeof document === "undefined") return;
|
|
183
|
-
const containers = document.querySelectorAll('.api-changelog-container');
|
|
184
|
-
|
|
185
|
-
await Promise.all(
|
|
186
|
-
Array.from(containers).map(async (container) => {
|
|
187
|
-
if (container.getAttribute('data-changelog-ready') === 'true') return;
|
|
188
|
-
container.setAttribute('data-changelog-ready', 'true');
|
|
189
|
-
|
|
190
|
-
const component = container.getAttribute('data-component');
|
|
191
|
-
|
|
192
|
-
const loadingEl = container.querySelector('.api-changelog-loading') as HTMLElement;
|
|
193
|
-
const contentEl = container.querySelector('.api-changelog-content') as HTMLElement;
|
|
194
|
-
const targetEl = container.querySelector('.api-changelog-timeline-target') as HTMLElement;
|
|
195
|
-
const emptyEl = container.querySelector('.api-changelog-empty') as HTMLElement;
|
|
196
|
-
|
|
197
|
-
if (!component || !loadingEl || !contentEl || !targetEl || !emptyEl) return;
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
const res = await fetch(`/api/github/changelog?component=${component}`);
|
|
201
|
-
if (!res.ok) throw new Error();
|
|
202
|
-
const data: any[] = await res.json();
|
|
203
|
-
|
|
204
|
-
loadingEl.classList.add('hidden');
|
|
205
|
-
|
|
206
|
-
if (!data || data.length === 0) {
|
|
207
|
-
emptyEl.classList.remove('hidden');
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const timelineItems: Array<{ type: 'tag' | 'commit'; versionTag?: string; sha?: string; message?: string }> = [];
|
|
212
|
-
|
|
213
|
-
data.forEach((release: any) => {
|
|
214
|
-
if (release.tag) {
|
|
215
|
-
timelineItems.push({
|
|
216
|
-
type: 'tag',
|
|
217
|
-
versionTag: release.tag === 'unreleased' ? 'Soon' : release.tag
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (release.commits && Array.isArray(release.commits)) {
|
|
222
|
-
release.commits.forEach((commit: any) => {
|
|
223
|
-
timelineItems.push({
|
|
224
|
-
type: 'commit',
|
|
225
|
-
sha: commit.sha,
|
|
226
|
-
message: commit.message
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
let rootHtml = `<div data-orientation="vertical" data-slot="root" class="flex flex-col">`;
|
|
233
|
-
|
|
234
|
-
timelineItems.forEach((item, index) => {
|
|
235
|
-
const isLast = index === timelineItems.length - 1;
|
|
236
|
-
|
|
237
|
-
if (item.type === 'tag') {
|
|
238
|
-
rootHtml += `
|
|
239
|
-
<div data-slot="item" class="group relative flex flex-1 gap-3">
|
|
240
|
-
<div data-slot="container" class="relative flex items-center flex-col shrink-0">
|
|
241
|
-
<span data-slot="indicator" class="inline-flex items-center justify-center shrink-0 select-none rounded-full align-middle relative size-6 text-xs text-primary bg-primary/10 border border-primary/20 my-1">
|
|
242
|
-
<span class="i-lucide-tag size-3.5"></span>
|
|
243
|
-
</span>
|
|
244
|
-
${!isLast ? `<div data-slot="separator" class="flex-1 bg-elevated w-0.5 min-h-[16px]"></div>` : ''}
|
|
245
|
-
</div>
|
|
246
|
-
<div data-slot="wrapper" class="w-full py-1">
|
|
247
|
-
<div data-slot="title" class="font-medium text-foreground text-sm flex items-center">
|
|
248
|
-
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-mono font-semibold bg-primary/10 text-primary border border-primary/20">
|
|
249
|
-
${escapeHtml(item.versionTag || '')}
|
|
250
|
-
</span>
|
|
251
|
-
</div>
|
|
252
|
-
</div>
|
|
253
|
-
</div>
|
|
254
|
-
`;
|
|
255
|
-
} else {
|
|
256
|
-
const cleanedMessage = (item.message || '')
|
|
257
|
-
.replace(/#(\d+)/g, '<a href="https://github.com/Rimelight-Entertainment/rimelight/issues/$1" target="_blank" class="text-primary hover:underline">#$1</a>')
|
|
258
|
-
.replace(/`(.*?)`/g, '<code class="text-xs px-1.5 py-0.5 bg-muted border border-border rounded font-mono text-muted-foreground">$1</code>');
|
|
259
|
-
|
|
260
|
-
rootHtml += `
|
|
261
|
-
<div data-slot="item" class="group relative flex flex-1 gap-3">
|
|
262
|
-
<div data-slot="container" class="relative flex items-center flex-col shrink-0">
|
|
263
|
-
<span data-slot="indicator" class="inline-flex items-center justify-center shrink-0 select-none rounded-full align-middle relative size-6 text-xs text-muted bg-transparent">
|
|
264
|
-
<span class="size-1.5 rounded-full bg-border"></span>
|
|
265
|
-
</span>
|
|
266
|
-
${!isLast ? `<div data-slot="separator" class="flex-1 bg-elevated w-0.5 min-h-[16px]"></div>` : ''}
|
|
267
|
-
</div>
|
|
268
|
-
<div data-slot="wrapper" class="w-full py-1 flex items-start gap-2 text-sm">
|
|
269
|
-
<a href="https://github.com/Rimelight-Entertainment/rimelight/commit/${item.sha}" target="_blank" class="font-mono text-xs px-1.5 py-0.5 bg-muted border border-border rounded text-muted-foreground hover:text-foreground hover:border-border shrink-0">
|
|
270
|
-
${item.sha ? item.sha.slice(0, 7) : ''}
|
|
271
|
-
</a>
|
|
272
|
-
<span class="text-muted-foreground text-sm leading-snug pt-0.5">
|
|
273
|
-
${cleanedMessage}
|
|
274
|
-
</span>
|
|
275
|
-
</div>
|
|
276
|
-
</div>
|
|
277
|
-
`;
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
rootHtml += `</div>`;
|
|
282
|
-
|
|
283
|
-
targetEl.innerHTML = rootHtml;
|
|
284
|
-
contentEl.classList.remove('hidden');
|
|
285
|
-
} catch (err) {
|
|
286
|
-
loadingEl.classList.add('hidden');
|
|
287
|
-
emptyEl.textContent = 'Failed to load changelog.';
|
|
288
|
-
emptyEl.classList.remove('hidden');
|
|
289
|
-
}
|
|
290
|
-
})
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function setupThemeCollapser() {
|
|
295
|
-
if (typeof document === 'undefined') return;
|
|
296
|
-
const containers = document.querySelectorAll('.api-theme-container');
|
|
297
|
-
containers.forEach(container => {
|
|
298
|
-
if (container.getAttribute('data-collapser-ready') === 'true') return;
|
|
299
|
-
container.setAttribute('data-collapser-ready', 'true');
|
|
300
|
-
|
|
301
|
-
const collapseWrapper = container.querySelector('.api-theme-collapse') as HTMLElement;
|
|
302
|
-
const fadeOverlay = container.querySelector('.api-theme-fade') as HTMLElement;
|
|
303
|
-
const btn = container.querySelector('.api-theme-btn') as HTMLButtonElement;
|
|
304
|
-
const triggerBar = container.querySelector('.api-theme-trigger-bar') as HTMLElement;
|
|
305
|
-
|
|
306
|
-
if (!collapseWrapper || !btn || !triggerBar || !fadeOverlay) return;
|
|
307
|
-
|
|
308
|
-
const checkAndSetup = () => {
|
|
309
|
-
const scrollHeight = collapseWrapper.scrollHeight;
|
|
310
|
-
if (scrollHeight <= 240) {
|
|
311
|
-
collapseWrapper.style.maxHeight = 'none';
|
|
312
|
-
fadeOverlay.style.display = 'none';
|
|
313
|
-
triggerBar.style.display = 'none';
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
let isExpanded = false;
|
|
318
|
-
|
|
319
|
-
btn.addEventListener('click', () => {
|
|
320
|
-
isExpanded = !isExpanded;
|
|
321
|
-
if (isExpanded) {
|
|
322
|
-
collapseWrapper.style.maxHeight = `${collapseWrapper.scrollHeight}px`;
|
|
323
|
-
btn.textContent = 'Hide theme config';
|
|
324
|
-
fadeOverlay.style.opacity = '0';
|
|
325
|
-
triggerBar.classList.remove('absolute', 'bottom-0');
|
|
326
|
-
triggerBar.classList.add('relative', 'bg-background', 'py-3', 'border-t', 'border-border');
|
|
327
|
-
triggerBar.style.paddingTop = '12px';
|
|
328
|
-
} else {
|
|
329
|
-
collapseWrapper.style.maxHeight = '240px';
|
|
330
|
-
btn.textContent = 'Show theme config';
|
|
331
|
-
fadeOverlay.style.opacity = '1';
|
|
332
|
-
triggerBar.classList.add('absolute', 'bottom-0');
|
|
333
|
-
triggerBar.classList.remove('relative', 'bg-background', 'py-3', 'border-t', 'border-border');
|
|
334
|
-
triggerBar.style.paddingTop = '48px';
|
|
335
|
-
container.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
setTimeout(checkAndSetup, 150);
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (typeof document !== 'undefined') {
|
|
345
|
-
loadChangelog();
|
|
346
|
-
setupThemeCollapser();
|
|
347
|
-
document.addEventListener('astro:page-load', loadChangelog);
|
|
348
|
-
document.addEventListener('astro:after-swap', setupThemeCollapser);
|
|
349
|
-
}
|
|
350
|
-
</script>
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import type { BaseBlock } from "@rimelight/cms"
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
block: BaseBlock
|
|
6
|
-
locale?: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const { block } = Astro.props
|
|
10
|
-
const { character, parenthetical, line } = block.props
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
<div class="my-3 mx-8 max-w-md">
|
|
14
|
-
<div class="text-sm font-bold text-center text-pink-800 dark:text-pink-200">
|
|
15
|
-
{character}
|
|
16
|
-
</div>
|
|
17
|
-
{parenthetical && (
|
|
18
|
-
<div class="text-xs italic text-center text-gray-600 dark:text-gray-400 mt-0.5">
|
|
19
|
-
({parenthetical})
|
|
20
|
-
</div>
|
|
21
|
-
)}
|
|
22
|
-
<div class="text-sm text-center text-gray-700 dark:text-gray-300 mt-1">{line}</div>
|
|
23
|
-
</div>
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import type { BaseBlock } from "@rimelight/cms"
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
block: BaseBlock
|
|
6
|
-
locale?: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const { block } = Astro.props
|
|
10
|
-
const { componentCode, framework = "astro", height = "300px", width = "100%", editable = false } = block.props
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
<div class="cms-live-preview border border-border rounded-xl overflow-visible bg-background text-foreground my-6" data-block-id={block.id} data-framework={framework} data-editable={editable}>
|
|
14
|
-
<div class="controls-area p-4 border-b border-border flex items-center justify-between">
|
|
15
|
-
<div class="flex items-center gap-2">
|
|
16
|
-
<span class="text-xs font-medium text-muted-foreground uppercase tracking-wider">Live Preview</span>
|
|
17
|
-
<span class="text-xs font-mono text-muted-foreground bg-muted px-2 py-0.5 rounded">{framework}</span>
|
|
18
|
-
</div>
|
|
19
|
-
{editable && (
|
|
20
|
-
<span class="text-xs text-muted-foreground italic">Editable</span>
|
|
21
|
-
)}
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<div class="preview-area p-4 flex items-center justify-center" style={`height: ${height}; width: ${width};`}>
|
|
25
|
-
<pre class="text-sm font-mono text-muted-foreground bg-muted p-4 rounded-lg overflow-x-auto w-full"><code>{componentCode}</code></pre>
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|