@rimelight/cms 0.0.1
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/README.md +19 -0
- package/package.json +69 -0
- package/src/astro/BlockRenderer.astro +111 -0
- package/src/astro/PageRenderer.astro +23 -0
- package/src/astro/blocks/CalloutBlock.astro +78 -0
- package/src/astro/blocks/CardBlock.astro +43 -0
- package/src/astro/blocks/CardsBlock.astro +24 -0
- package/src/astro/blocks/CodeBlock.astro +16 -0
- package/src/astro/blocks/DialogueBlock.astro +23 -0
- package/src/astro/blocks/FileTreeBlock.astro +45 -0
- package/src/astro/blocks/ImageBlock.astro +17 -0
- package/src/astro/blocks/OrderedListBlock.astro +55 -0
- package/src/astro/blocks/ParagraphBlock.astro +65 -0
- package/src/astro/blocks/SceneBlock.astro +65 -0
- package/src/astro/blocks/ScriptBlock.astro +59 -0
- package/src/astro/blocks/SectionBlock.astro +46 -0
- package/src/astro/blocks/StepItemBlock.astro +25 -0
- package/src/astro/blocks/StepsBlock.astro +14 -0
- package/src/astro/blocks/TabItemBlock.astro +19 -0
- package/src/astro/blocks/TableBlock.astro +49 -0
- package/src/astro/blocks/TabsBlock.astro +67 -0
- package/src/astro/blocks/UnorderedListBlock.astro +55 -0
- package/src/auth/editor-guards.ts +148 -0
- package/src/auth/filter-blocks.ts +44 -0
- package/src/auth/filter-templates.ts +81 -0
- package/src/auth/permissions.ts +40 -0
- package/src/cache/purge.ts +37 -0
- package/src/client/components/RimelightBlock.tsx +487 -0
- package/src/client/components/RimelightBlockPicker.tsx +305 -0
- package/src/client/components/RimelightEditor.tsx +131 -0
- package/src/client/components/RimelightEditorLayout.tsx +143 -0
- package/src/client/components/RimelightPreview.tsx +354 -0
- package/src/client/components/RimelightPropertiesPanel.tsx +408 -0
- package/src/client/components/RimelightTemplateEditor.tsx +245 -0
- package/src/client/components/editors/CalloutEditor.tsx +185 -0
- package/src/client/components/editors/CardEditor.tsx +60 -0
- package/src/client/components/editors/CardsEditor.tsx +132 -0
- package/src/client/components/editors/CodeEditor.tsx +101 -0
- package/src/client/components/editors/DialogueEditor.tsx +52 -0
- package/src/client/components/editors/FileTreeEditor.tsx +65 -0
- package/src/client/components/editors/ImageEditor.tsx +70 -0
- package/src/client/components/editors/ParagraphEditor.tsx +314 -0
- package/src/client/components/editors/SceneEditor.tsx +245 -0
- package/src/client/components/editors/ScriptEditor.tsx +245 -0
- package/src/client/components/editors/SectionEditor.tsx +139 -0
- package/src/client/components/editors/StepItemEditor.tsx +117 -0
- package/src/client/components/editors/StepsEditor.tsx +105 -0
- package/src/client/components/editors/TabItemEditor.tsx +113 -0
- package/src/client/components/editors/TableEditor.tsx +181 -0
- package/src/client/components/editors/TabsEditor.tsx +105 -0
- package/src/client/components/index.ts +7 -0
- package/src/client/loader.ts +19 -0
- package/src/client/state/autosave.ts +122 -0
- package/src/client/state/editor-store.ts +411 -0
- package/src/client/state/history.ts +251 -0
- package/src/client/state/lock-manager.ts +89 -0
- package/src/client/styles/cms-editor.css +653 -0
- package/src/client/toast.ts +12 -0
- package/src/client/utils/sortable.ts +184 -0
- package/src/client/utils/splitpane.ts +87 -0
- package/src/client/utils/tree-sanitizer.ts +33 -0
- package/src/core/page-definitions.ts +502 -0
- package/src/core/registry.ts +99 -0
- package/src/core/template-sync.ts +75 -0
- package/src/core/types/blocks.ts +367 -0
- package/src/core/types/inline.ts +23 -0
- package/src/core/types/pages.ts +36 -0
- package/src/core/types/templates.ts +32 -0
- package/src/core/types/versioning.ts +44 -0
- package/src/core/validator.ts +28 -0
- package/src/env.d.ts +4 -0
- package/src/index.ts +47 -0
- package/src/markdown/index.ts +2 -0
- package/src/markdown/parser.ts +347 -0
- package/src/markdown/serializer.ts +219 -0
- package/src/migration.ts +155 -0
- package/src/schema/index.ts +8 -0
- package/src/schema/page_draft_locks.ts +13 -0
- package/src/schema/page_drafts.ts +18 -0
- package/src/schema/page_templates.ts +27 -0
- package/src/schema/page_version_approvals.ts +12 -0
- package/src/schema/page_version_comments.ts +14 -0
- package/src/schema/page_versions.ts +34 -0
- package/src/schema/pages.ts +42 -0
- package/src/schema/search.ts +23 -0
- package/src/search/query.ts +10 -0
- package/src/search/sync.ts +32 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { createSignal, onMount, onCleanup, For, type Component } from "solid-js"
|
|
2
|
+
import { type BaseBlock, calculateHeadingLevel } from "../../core/types/blocks"
|
|
3
|
+
|
|
4
|
+
export interface RimelightPreviewProps {
|
|
5
|
+
blocks?: BaseBlock[] | (() => BaseBlock[]) | undefined
|
|
6
|
+
title?: string | undefined
|
|
7
|
+
description?: string | undefined
|
|
8
|
+
banner?: string | undefined
|
|
9
|
+
category?: string | undefined
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const RimelightPreview: Component<RimelightPreviewProps> = (props) => {
|
|
13
|
+
const getInitialBlocks = (): BaseBlock[] => {
|
|
14
|
+
if (typeof props.blocks === "function") {
|
|
15
|
+
try {
|
|
16
|
+
return props.blocks() || []
|
|
17
|
+
} catch {
|
|
18
|
+
return []
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(props.blocks)) {
|
|
22
|
+
return props.blocks
|
|
23
|
+
}
|
|
24
|
+
return []
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const [blocks, setBlocks] = createSignal<BaseBlock[]>(getInitialBlocks())
|
|
28
|
+
const [title, setTitle] = createSignal<string>(props.title || "")
|
|
29
|
+
const [description, setDescription] = createSignal<string>(props.description || "")
|
|
30
|
+
const [banner, setBanner] = createSignal<string>(props.banner || "")
|
|
31
|
+
const [category, setCategory] = createSignal<string>(props.category || "")
|
|
32
|
+
|
|
33
|
+
onMount(() => {
|
|
34
|
+
const handleBlocksChange = (e: Event) => {
|
|
35
|
+
if (e instanceof CustomEvent && Array.isArray(e.detail?.blocks)) {
|
|
36
|
+
setBlocks(e.detail.blocks)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const handleMetaChange = (e: Event) => {
|
|
41
|
+
if (e instanceof CustomEvent && e.detail) {
|
|
42
|
+
if (typeof e.detail.title === "string") setTitle(e.detail.title)
|
|
43
|
+
if (typeof e.detail.description === "string") setDescription(e.detail.description)
|
|
44
|
+
if (typeof e.detail.banner === "string") setBanner(e.detail.banner)
|
|
45
|
+
if (typeof e.detail.category === "string") setCategory(e.detail.category)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handlePropsChange = (e: Event) => {
|
|
50
|
+
if (e instanceof CustomEvent && e.detail?.properties) {
|
|
51
|
+
if (typeof e.detail.properties.heroImage === "string") {
|
|
52
|
+
setBanner(e.detail.properties.heroImage)
|
|
53
|
+
}
|
|
54
|
+
if (typeof e.detail.properties.category === "string") {
|
|
55
|
+
setCategory(e.detail.properties.category)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
document.addEventListener("blocks-change", handleBlocksChange)
|
|
61
|
+
document.addEventListener("page-meta-change", handleMetaChange)
|
|
62
|
+
document.addEventListener("properties-change", handlePropsChange)
|
|
63
|
+
|
|
64
|
+
onCleanup(() => {
|
|
65
|
+
document.removeEventListener("blocks-change", handleBlocksChange)
|
|
66
|
+
document.removeEventListener("page-meta-change", handleMetaChange)
|
|
67
|
+
document.removeEventListener("properties-change", handlePropsChange)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div class="cms-preview-pane flex-1 w-full h-full overflow-y-auto bg-transparent">
|
|
73
|
+
<div class="cms-preview-document p-6 sm:p-8 max-w-4xl mx-auto space-y-6">
|
|
74
|
+
{banner() && (
|
|
75
|
+
<div class="rounded-2xl overflow-hidden border border-default shadow-sm aspect-[21/9]">
|
|
76
|
+
<img src={banner()} alt={title() || "Banner"} class="w-full h-full object-cover" />
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{(title() || description() || category()) && (
|
|
81
|
+
<header class="border-b border-default pb-6 mb-6">
|
|
82
|
+
{category() && (
|
|
83
|
+
<span class="inline-block px-2.5 py-1 text-xs font-semibold rounded-full bg-primary/10 text-primary mb-3 uppercase tracking-wider">
|
|
84
|
+
{category()}
|
|
85
|
+
</span>
|
|
86
|
+
)}
|
|
87
|
+
{title() && (
|
|
88
|
+
<h1 class="text-3xl sm:text-4xl font-extrabold text-foreground tracking-tight leading-tight">
|
|
89
|
+
{title()}
|
|
90
|
+
</h1>
|
|
91
|
+
)}
|
|
92
|
+
{description() && (
|
|
93
|
+
<p class="text-lg text-dimmed mt-3 leading-relaxed">{description()}</p>
|
|
94
|
+
)}
|
|
95
|
+
</header>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
<div class="space-y-4">
|
|
99
|
+
{blocks().length === 0 ? (
|
|
100
|
+
<div class="text-center py-12 text-neutral-500 text-sm italic">
|
|
101
|
+
No blocks in this page yet.
|
|
102
|
+
</div>
|
|
103
|
+
) : (
|
|
104
|
+
<For each={blocks()}>{(b) => <div innerHTML={renderBlockPreviewHtml(b, 0)} />}</For>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function escapeHtml(str: string): string {
|
|
113
|
+
return (str || "")
|
|
114
|
+
.replace(/&/g, "&")
|
|
115
|
+
.replace(/</g, "<")
|
|
116
|
+
.replace(/>/g, ">")
|
|
117
|
+
.replace(/"/g, """)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getChildren(b: BaseBlock): BaseBlock[] {
|
|
121
|
+
if (!b) return []
|
|
122
|
+
if (Array.isArray(b.children)) return b.children
|
|
123
|
+
if (Array.isArray(b.props?.children)) return b.props.children
|
|
124
|
+
return []
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function renderBlockPreviewHtml(b: BaseBlock, sectionDepth: number = 0): string {
|
|
128
|
+
if (!b) return ""
|
|
129
|
+
const props = b.props || {}
|
|
130
|
+
const rawText = props.text
|
|
131
|
+
const textVal = typeof rawText === "object" && rawText !== null ? rawText.en || "" : rawText || ""
|
|
132
|
+
|
|
133
|
+
if (b.type === "ParagraphBlock") {
|
|
134
|
+
const renderNodes = (nodes: any[]): string => {
|
|
135
|
+
return nodes
|
|
136
|
+
.map((node) => {
|
|
137
|
+
if (!node) return ""
|
|
138
|
+
if (node.type === "text") {
|
|
139
|
+
let t = escapeHtml(node.text || "")
|
|
140
|
+
if (node.marks && Array.isArray(node.marks)) {
|
|
141
|
+
if (node.marks.includes("bold")) t = `<strong>${t}</strong>`
|
|
142
|
+
if (node.marks.includes("italic")) t = `<em>${t}</em>`
|
|
143
|
+
if (node.marks.includes("underline")) t = `<u>${t}</u>`
|
|
144
|
+
if (node.marks.includes("strikethrough")) t = `<s>${t}</s>`
|
|
145
|
+
if (node.marks.includes("code"))
|
|
146
|
+
t = `<code class="bg-neutral-800 text-neutral-200 px-1.5 py-0.5 rounded text-sm font-mono">${t}</code>`
|
|
147
|
+
}
|
|
148
|
+
return t
|
|
149
|
+
}
|
|
150
|
+
if (node.type === "link") {
|
|
151
|
+
const inner = node.children ? renderNodes(node.children) : escapeHtml(node.text || "")
|
|
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>`
|
|
154
|
+
}
|
|
155
|
+
if (node.type === "page_mention") {
|
|
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>`
|
|
158
|
+
}
|
|
159
|
+
return ""
|
|
160
|
+
})
|
|
161
|
+
.join("")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let html = ""
|
|
165
|
+
if (Array.isArray(rawText)) {
|
|
166
|
+
html = renderNodes(rawText)
|
|
167
|
+
} else {
|
|
168
|
+
html = escapeHtml(textVal)
|
|
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>`
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (b.type === "SectionBlock") {
|
|
174
|
+
const level = calculateHeadingLevel(sectionDepth)
|
|
175
|
+
const title = props.title || "Untitled Section"
|
|
176
|
+
const slug = title.toLowerCase().replace(/\W+/g, "-")
|
|
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"
|
|
181
|
+
else if (level === 3) headingClasses += " text-2xl font-semibold mt-5 mb-2.5"
|
|
182
|
+
else if (level === 4) headingClasses += " text-xl font-medium mt-4 mb-2"
|
|
183
|
+
else if (level === 5) headingClasses += " text-lg font-medium mt-3 mb-1.5"
|
|
184
|
+
else headingClasses += " text-base font-normal mt-2 mb-1"
|
|
185
|
+
|
|
186
|
+
return `<div class="section-block my-6">
|
|
187
|
+
<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">
|
|
190
|
+
${getChildren(b)
|
|
191
|
+
.map((c) => renderBlockPreviewHtml(c, sectionDepth + 1))
|
|
192
|
+
.join("")}
|
|
193
|
+
</div>
|
|
194
|
+
</div>`
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (b.type === "CalloutBlock") {
|
|
198
|
+
const variant = props.variant || "info"
|
|
199
|
+
const to = props.to
|
|
200
|
+
const target = props.target || "_self"
|
|
201
|
+
const icons: Record<string, string> = {
|
|
202
|
+
info: "ℹ️",
|
|
203
|
+
success: "✅",
|
|
204
|
+
warning: "⚠️",
|
|
205
|
+
error: "🛑",
|
|
206
|
+
commentary: "💬",
|
|
207
|
+
ideation: "💡",
|
|
208
|
+
source: "💻"
|
|
209
|
+
}
|
|
210
|
+
const icon = icons[variant] || icons.info
|
|
211
|
+
|
|
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"
|
|
214
|
+
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"
|
|
217
|
+
} 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"
|
|
220
|
+
} 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"
|
|
223
|
+
} 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"
|
|
226
|
+
} 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"
|
|
229
|
+
} 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"
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const tag = to ? `a href="${escapeHtml(to)}" target="${escapeHtml(target)}"` : "div"
|
|
235
|
+
const closeTag = to ? "a" : "div"
|
|
236
|
+
|
|
237
|
+
return `<${tag} class="callout border rounded-xl p-4 my-4 flex items-start gap-3 text-sm no-underline ${variantClasses}">
|
|
238
|
+
<span class="text-base shrink-0 mt-0.5 select-none">${icon}</span>
|
|
239
|
+
<div class="flex-1 min-w-0 text-sm">
|
|
240
|
+
${getChildren(b)
|
|
241
|
+
.map((c) => renderBlockPreviewHtml(c, sectionDepth))
|
|
242
|
+
.join("")}
|
|
243
|
+
</div>
|
|
244
|
+
</${closeTag}>`
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (b.type === "ImageBlock") {
|
|
248
|
+
const src = props.src || props.url || ""
|
|
249
|
+
const alt = props.alt || "Image"
|
|
250
|
+
const caption = props.caption
|
|
251
|
+
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>` : ""}
|
|
254
|
+
</figure>`
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (b.type === "CodeBlock") {
|
|
258
|
+
const code = props.code || textVal
|
|
259
|
+
const language = props.language || "plaintext"
|
|
260
|
+
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>` : ""}
|
|
265
|
+
</div>`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (b.type === "ScriptBlock") {
|
|
269
|
+
const title = props.title || "Untitled Script"
|
|
270
|
+
const synopsis = props.synopsis
|
|
271
|
+
const chars = props.characters || []
|
|
272
|
+
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">
|
|
274
|
+
<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>` : ""}
|
|
278
|
+
</div>
|
|
279
|
+
${
|
|
280
|
+
aPlot || bPlot || cPlot
|
|
281
|
+
? `<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>` : ""}
|
|
285
|
+
</div>`
|
|
286
|
+
: ""
|
|
287
|
+
}
|
|
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>` : ""}
|
|
289
|
+
<div class="mt-4">
|
|
290
|
+
${getChildren(b)
|
|
291
|
+
.map((c) => renderBlockPreviewHtml(c, sectionDepth))
|
|
292
|
+
.join("")}
|
|
293
|
+
</div>
|
|
294
|
+
</div>`
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (b.type === "SceneBlock") {
|
|
298
|
+
const settingLabels: Record<string, string> = {
|
|
299
|
+
INTERIOR: "INT.",
|
|
300
|
+
EXTERIOR: "EXT.",
|
|
301
|
+
OTHER: "Other"
|
|
302
|
+
}
|
|
303
|
+
const timeOfDayLabels: Record<string, string> = {
|
|
304
|
+
MORNING: "Morning",
|
|
305
|
+
NOON: "Noon",
|
|
306
|
+
AFTERNOON: "Afternoon",
|
|
307
|
+
EVENING: "Evening",
|
|
308
|
+
NIGHT: "Night",
|
|
309
|
+
OTHER: "Other"
|
|
310
|
+
}
|
|
311
|
+
const transitionLabels: Record<string, string> = {
|
|
312
|
+
CUT_TO: "CUT TO",
|
|
313
|
+
CUT_BACK_TO: "CUT BACK TO",
|
|
314
|
+
FADE_TO: "FADE TO",
|
|
315
|
+
NONE: ""
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const setting = settingLabels[props.setting] || props.setting || "INT."
|
|
319
|
+
const location = props.location || ""
|
|
320
|
+
const tod = timeOfDayLabels[props.timeOfDay] || props.timeOfDay || "Morning"
|
|
321
|
+
const transition = transitionLabels[props.transition] || ""
|
|
322
|
+
|
|
323
|
+
const parts: string[] = []
|
|
324
|
+
if (setting && setting !== "Other") parts.push(setting)
|
|
325
|
+
if (location) parts.push(location)
|
|
326
|
+
if (tod && tod !== "Other") parts.push(`- ${tod}`)
|
|
327
|
+
|
|
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">
|
|
330
|
+
${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>` : ""}
|
|
332
|
+
</div>
|
|
333
|
+
${props.description ? `<p class="text-sm text-gray-700 dark:text-gray-300 mb-2">${escapeHtml(props.description)}</p>` : ""}
|
|
334
|
+
<div class="mt-3">
|
|
335
|
+
${getChildren(b)
|
|
336
|
+
.map((c) => renderBlockPreviewHtml(c, sectionDepth))
|
|
337
|
+
.join("")}
|
|
338
|
+
</div>
|
|
339
|
+
</div>`
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (b.type === "DialogueBlock") {
|
|
343
|
+
const character = props.character || "CHARACTER"
|
|
344
|
+
const parenthetical = props.parenthetical
|
|
345
|
+
const line = props.line || ""
|
|
346
|
+
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>
|
|
350
|
+
</div>`
|
|
351
|
+
}
|
|
352
|
+
|
|
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>`
|
|
354
|
+
}
|