@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.
Files changed (87) hide show
  1. package/README.md +19 -0
  2. package/package.json +69 -0
  3. package/src/astro/BlockRenderer.astro +111 -0
  4. package/src/astro/PageRenderer.astro +23 -0
  5. package/src/astro/blocks/CalloutBlock.astro +78 -0
  6. package/src/astro/blocks/CardBlock.astro +43 -0
  7. package/src/astro/blocks/CardsBlock.astro +24 -0
  8. package/src/astro/blocks/CodeBlock.astro +16 -0
  9. package/src/astro/blocks/DialogueBlock.astro +23 -0
  10. package/src/astro/blocks/FileTreeBlock.astro +45 -0
  11. package/src/astro/blocks/ImageBlock.astro +17 -0
  12. package/src/astro/blocks/OrderedListBlock.astro +55 -0
  13. package/src/astro/blocks/ParagraphBlock.astro +65 -0
  14. package/src/astro/blocks/SceneBlock.astro +65 -0
  15. package/src/astro/blocks/ScriptBlock.astro +59 -0
  16. package/src/astro/blocks/SectionBlock.astro +46 -0
  17. package/src/astro/blocks/StepItemBlock.astro +25 -0
  18. package/src/astro/blocks/StepsBlock.astro +14 -0
  19. package/src/astro/blocks/TabItemBlock.astro +19 -0
  20. package/src/astro/blocks/TableBlock.astro +49 -0
  21. package/src/astro/blocks/TabsBlock.astro +67 -0
  22. package/src/astro/blocks/UnorderedListBlock.astro +55 -0
  23. package/src/auth/editor-guards.ts +148 -0
  24. package/src/auth/filter-blocks.ts +44 -0
  25. package/src/auth/filter-templates.ts +81 -0
  26. package/src/auth/permissions.ts +40 -0
  27. package/src/cache/purge.ts +37 -0
  28. package/src/client/components/RimelightBlock.tsx +487 -0
  29. package/src/client/components/RimelightBlockPicker.tsx +305 -0
  30. package/src/client/components/RimelightEditor.tsx +131 -0
  31. package/src/client/components/RimelightEditorLayout.tsx +143 -0
  32. package/src/client/components/RimelightPreview.tsx +354 -0
  33. package/src/client/components/RimelightPropertiesPanel.tsx +408 -0
  34. package/src/client/components/RimelightTemplateEditor.tsx +245 -0
  35. package/src/client/components/editors/CalloutEditor.tsx +185 -0
  36. package/src/client/components/editors/CardEditor.tsx +60 -0
  37. package/src/client/components/editors/CardsEditor.tsx +132 -0
  38. package/src/client/components/editors/CodeEditor.tsx +101 -0
  39. package/src/client/components/editors/DialogueEditor.tsx +52 -0
  40. package/src/client/components/editors/FileTreeEditor.tsx +65 -0
  41. package/src/client/components/editors/ImageEditor.tsx +70 -0
  42. package/src/client/components/editors/ParagraphEditor.tsx +314 -0
  43. package/src/client/components/editors/SceneEditor.tsx +245 -0
  44. package/src/client/components/editors/ScriptEditor.tsx +245 -0
  45. package/src/client/components/editors/SectionEditor.tsx +139 -0
  46. package/src/client/components/editors/StepItemEditor.tsx +117 -0
  47. package/src/client/components/editors/StepsEditor.tsx +105 -0
  48. package/src/client/components/editors/TabItemEditor.tsx +113 -0
  49. package/src/client/components/editors/TableEditor.tsx +181 -0
  50. package/src/client/components/editors/TabsEditor.tsx +105 -0
  51. package/src/client/components/index.ts +7 -0
  52. package/src/client/loader.ts +19 -0
  53. package/src/client/state/autosave.ts +122 -0
  54. package/src/client/state/editor-store.ts +411 -0
  55. package/src/client/state/history.ts +251 -0
  56. package/src/client/state/lock-manager.ts +89 -0
  57. package/src/client/styles/cms-editor.css +653 -0
  58. package/src/client/toast.ts +12 -0
  59. package/src/client/utils/sortable.ts +184 -0
  60. package/src/client/utils/splitpane.ts +87 -0
  61. package/src/client/utils/tree-sanitizer.ts +33 -0
  62. package/src/core/page-definitions.ts +502 -0
  63. package/src/core/registry.ts +99 -0
  64. package/src/core/template-sync.ts +75 -0
  65. package/src/core/types/blocks.ts +367 -0
  66. package/src/core/types/inline.ts +23 -0
  67. package/src/core/types/pages.ts +36 -0
  68. package/src/core/types/templates.ts +32 -0
  69. package/src/core/types/versioning.ts +44 -0
  70. package/src/core/validator.ts +28 -0
  71. package/src/env.d.ts +4 -0
  72. package/src/index.ts +47 -0
  73. package/src/markdown/index.ts +2 -0
  74. package/src/markdown/parser.ts +347 -0
  75. package/src/markdown/serializer.ts +219 -0
  76. package/src/migration.ts +155 -0
  77. package/src/schema/index.ts +8 -0
  78. package/src/schema/page_draft_locks.ts +13 -0
  79. package/src/schema/page_drafts.ts +18 -0
  80. package/src/schema/page_templates.ts +27 -0
  81. package/src/schema/page_version_approvals.ts +12 -0
  82. package/src/schema/page_version_comments.ts +14 -0
  83. package/src/schema/page_versions.ts +34 -0
  84. package/src/schema/pages.ts +42 -0
  85. package/src/schema/search.ts +23 -0
  86. package/src/search/query.ts +10 -0
  87. package/src/search/sync.ts +32 -0
@@ -0,0 +1,411 @@
1
+ import { createSignal } from "solid-js"
2
+ import { createStore, reconcile } from "solid-js/store"
3
+ import { type BaseBlock, MAX_SECTION_DEPTH } from "../../core/types/blocks"
4
+ import { HistoryStack, createDefaultBlock } from "./history"
5
+ import { AutosaveController, type AutosaveStatus } from "./autosave"
6
+ import { sanitizeBlockTree } from "../utils/tree-sanitizer"
7
+ import { toast } from "../toast"
8
+
9
+ const cloneBlocks = (b: BaseBlock[]): BaseBlock[] => JSON.parse(JSON.stringify(b))
10
+
11
+ function findBlockInfo(
12
+ list: BaseBlock[],
13
+ targetId: string,
14
+ currentDepth: number = 0
15
+ ): { block: BaseBlock; depth: number; parentId: string | null } | null {
16
+ for (const b of list) {
17
+ if (!b) continue
18
+ if (b.id === targetId) {
19
+ return { block: b, depth: currentDepth, parentId: null }
20
+ }
21
+ const isSec = b.type === "SectionBlock"
22
+ const nextDepth = isSec ? currentDepth + 1 : currentDepth
23
+ const children = b.children || b.props?.children
24
+ if (Array.isArray(children)) {
25
+ const found = findBlockInfo(children, targetId, nextDepth)
26
+ if (found) {
27
+ if (!found.parentId && b.id) {
28
+ found.parentId = b.id
29
+ }
30
+ return found
31
+ }
32
+ }
33
+ }
34
+ return null
35
+ }
36
+
37
+ function getMaxSubtreeSectionHeight(b: BaseBlock | null): number {
38
+ if (!b) return 0
39
+ const isSec = b.type === "SectionBlock"
40
+ const children = b.children || b.props?.children
41
+ let maxChildHeight = 0
42
+ if (Array.isArray(children)) {
43
+ for (const c of children) {
44
+ const h = getMaxSubtreeSectionHeight(c)
45
+ if (h > maxChildHeight) maxChildHeight = h
46
+ }
47
+ }
48
+ return isSec ? 1 + maxChildHeight : maxChildHeight
49
+ }
50
+
51
+ export function createEditorState(
52
+ initialBlocks: BaseBlock[] = [],
53
+ pageId: string = "default-page"
54
+ ) {
55
+ const [blocks, setBlocksStore] = createStore<BaseBlock[]>(
56
+ JSON.parse(JSON.stringify(initialBlocks))
57
+ )
58
+ const [status, setStatus] = createSignal<AutosaveStatus>("saved")
59
+ const [previewVisible, setPreviewVisible] = createSignal<boolean>(false)
60
+ const [pickerTarget, setPickerTarget] = createSignal<{
61
+ open: boolean
62
+ targetId: string | null
63
+ position: "before" | "after"
64
+ sectionDepth: number
65
+ parentId?: string | null
66
+ containerType?: string | null
67
+ }>({
68
+ open: false,
69
+ targetId: null,
70
+ position: "after",
71
+ sectionDepth: 0,
72
+ parentId: null,
73
+ containerType: null
74
+ })
75
+
76
+ let historyStack = new HistoryStack(initialBlocks)
77
+ let autosaveController = new AutosaveController({
78
+ pageId,
79
+ onStatusChange: (newStatus: AutosaveStatus) => {
80
+ setStatus(newStatus)
81
+ }
82
+ })
83
+
84
+ const notifyChange = (immediate = false) => {
85
+ const rawBlocks = sanitizeBlockTree(JSON.parse(JSON.stringify(blocks)))
86
+ historyStack.push(rawBlocks, immediate)
87
+ if (immediate) {
88
+ void autosaveController.saveImmediate({ blocks: rawBlocks })
89
+ } else {
90
+ autosaveController.notifyChange({ blocks: rawBlocks })
91
+ }
92
+ }
93
+
94
+ const setBlocks = (newBlocks: BaseBlock[]) => {
95
+ historyStack.pause()
96
+ setBlocksStore(reconcile(JSON.parse(JSON.stringify(newBlocks))))
97
+ historyStack = new HistoryStack(newBlocks)
98
+ historyStack.resume()
99
+ notifyChange(true)
100
+ }
101
+
102
+ const updateBlockProps = (id: string, newProps: Record<string, any>) => {
103
+ const updated = cloneBlocks(blocks)
104
+ const findAndUpdate = (list: BaseBlock[]): boolean => {
105
+ for (const item of list) {
106
+ if (!item) continue
107
+ if (item.id === id) {
108
+ item.props = { ...item.props, ...newProps }
109
+ if (newProps.children !== undefined) {
110
+ item.children = newProps.children
111
+ item.props.children = newProps.children
112
+ }
113
+ return true
114
+ }
115
+ const childrenList = item.children || item.props?.children
116
+ if (childrenList && findAndUpdate(childrenList)) {
117
+ return true
118
+ }
119
+ }
120
+ return false
121
+ }
122
+ if (findAndUpdate(updated)) {
123
+ setBlocksStore(reconcile(updated))
124
+ notifyChange()
125
+ }
126
+ }
127
+
128
+ const undo = () => {
129
+ const prev = historyStack.undo()
130
+ if (prev) {
131
+ setBlocksStore(reconcile(prev))
132
+ autosaveController.notifyChange({ blocks: prev })
133
+ }
134
+ }
135
+
136
+ const redo = () => {
137
+ const next = historyStack.redo()
138
+ if (next) {
139
+ setBlocksStore(reconcile(next))
140
+ autosaveController.notifyChange({ blocks: next })
141
+ }
142
+ }
143
+
144
+ const deleteBlock = (id: string) => {
145
+ const removeFromList = (list: BaseBlock[]): BaseBlock[] => {
146
+ return list.filter((b) => {
147
+ if (b.id === id) return false
148
+ if (b.children) {
149
+ b.children = removeFromList(b.children)
150
+ }
151
+ if (b.props?.children) {
152
+ b.props.children = removeFromList(b.props.children)
153
+ }
154
+ return true
155
+ })
156
+ }
157
+ const updated = removeFromList(JSON.parse(JSON.stringify(blocks)))
158
+ setBlocksStore(reconcile(updated))
159
+ notifyChange(true)
160
+ }
161
+
162
+ const moveBlock = (id: string, direction: -1 | 1) => {
163
+ const updated = cloneBlocks(blocks)
164
+ const findAndMove = (list: BaseBlock[]): boolean => {
165
+ const idx = list.findIndex((b) => b.id === id)
166
+ const newIdx = idx + direction
167
+ if (idx !== -1 && newIdx >= 0 && newIdx < list.length) {
168
+ const item = list.splice(idx, 1)[0]
169
+ if (item) {
170
+ list.splice(newIdx, 0, item)
171
+ return true
172
+ }
173
+ }
174
+ for (const item of list) {
175
+ const children = item.children || item.props?.children
176
+ if (children && findAndMove(children)) {
177
+ item.children = children
178
+ item.props.children = children
179
+ return true
180
+ }
181
+ }
182
+ return false
183
+ }
184
+ findAndMove(updated)
185
+ setBlocksStore(reconcile(updated))
186
+ notifyChange(true)
187
+ }
188
+
189
+ const duplicateBlock = (id: string) => {
190
+ const updated = cloneBlocks(blocks)
191
+ const findAndDup = (list: BaseBlock[]): boolean => {
192
+ const idx = list.findIndex((b) => b.id === id)
193
+ if (idx !== -1) {
194
+ const clone: BaseBlock = JSON.parse(JSON.stringify(list[idx]))
195
+ clone.id =
196
+ typeof crypto !== "undefined" && crypto.randomUUID
197
+ ? crypto.randomUUID()
198
+ : Math.random().toString(36).slice(2)
199
+ list.splice(idx + 1, 0, clone)
200
+ return true
201
+ }
202
+ for (const item of list) {
203
+ const children = item.children || item.props?.children
204
+ if (children && findAndDup(children)) {
205
+ item.children = children
206
+ item.props.children = children
207
+ return true
208
+ }
209
+ }
210
+ return false
211
+ }
212
+ findAndDup(updated)
213
+ setBlocksStore(reconcile(updated))
214
+ notifyChange(true)
215
+ }
216
+
217
+ const insertBlock = (
218
+ type: string,
219
+ targetId: string | null = null,
220
+ position: "before" | "after" = "after",
221
+ parentId: string | null = null
222
+ ) => {
223
+ const updated = cloneBlocks(blocks)
224
+
225
+ if (type === "SectionBlock") {
226
+ let targetSectionDepth = 0
227
+ if (parentId) {
228
+ const parentInfo = findBlockInfo(updated, parentId, 0)
229
+ if (parentInfo) {
230
+ const isParentSection = parentInfo.block.type === "SectionBlock"
231
+ targetSectionDepth = isParentSection ? parentInfo.depth + 1 : parentInfo.depth
232
+ }
233
+ } else if (targetId) {
234
+ const targetInfo = findBlockInfo(updated, targetId, 0)
235
+ if (targetInfo) {
236
+ targetSectionDepth = targetInfo.depth
237
+ }
238
+ }
239
+
240
+ if (targetSectionDepth > MAX_SECTION_DEPTH) {
241
+ toast.error(
242
+ "Invalid Section Nesting",
243
+ "Sections cannot be nested inside an H6 section. H6 is the maximum heading level."
244
+ )
245
+ return
246
+ }
247
+ }
248
+
249
+ const newBlock = createDefaultBlock(type)
250
+
251
+ const insertIntoList = (list: BaseBlock[]): boolean => {
252
+ if (targetId) {
253
+ const idx = list.findIndex((b) => b.id === targetId)
254
+ if (idx !== -1) {
255
+ list.splice(position === "after" ? idx + 1 : idx, 0, newBlock)
256
+ return true
257
+ }
258
+ }
259
+ if (parentId) {
260
+ for (const item of list) {
261
+ if (item.id === parentId) {
262
+ const children = item.children || item.props?.children || []
263
+ if (position === "after") {
264
+ children.push(newBlock)
265
+ } else {
266
+ children.unshift(newBlock)
267
+ }
268
+ item.children = children
269
+ item.props.children = children
270
+ return true
271
+ }
272
+ }
273
+ }
274
+ for (const item of list) {
275
+ const children = item.children || item.props?.children
276
+ if (children && insertIntoList(children)) {
277
+ item.children = children
278
+ item.props.children = children
279
+ return true
280
+ }
281
+ }
282
+ return false
283
+ }
284
+
285
+ if (!targetId && !parentId) {
286
+ updated.push(newBlock)
287
+ } else {
288
+ const inserted = insertIntoList(updated)
289
+ if (!inserted) updated.push(newBlock)
290
+ }
291
+
292
+ setBlocksStore(reconcile(updated))
293
+ notifyChange(true)
294
+ }
295
+
296
+ const openPicker = (
297
+ targetId: string | null = null,
298
+ position: "before" | "after" = "after",
299
+ sectionDepth = 0,
300
+ parentId: string | null = null,
301
+ containerType: string | null = null
302
+ ) => {
303
+ setPickerTarget({ open: true, targetId, position, sectionDepth, parentId, containerType })
304
+ }
305
+
306
+ const closePicker = () => {
307
+ setPickerTarget((prev) => ({ ...prev, open: false }))
308
+ }
309
+
310
+ const relocateBlock = (id: string, newIndex: number, targetParentId?: string | null) => {
311
+ const updated = cloneBlocks(blocks)
312
+ let extractedBlock: BaseBlock | null = null
313
+
314
+ const remove = (list: BaseBlock[]): BaseBlock[] => {
315
+ return list.filter((b) => {
316
+ if (b.id === id) {
317
+ extractedBlock = b
318
+ return false
319
+ }
320
+ if (b.children) {
321
+ b.children = remove(b.children)
322
+ if (b.props) b.props.children = b.children
323
+ } else if (b.props?.children) {
324
+ b.props.children = remove(b.props.children)
325
+ b.children = b.props.children
326
+ }
327
+ return true
328
+ })
329
+ }
330
+
331
+ const cleanedTree = remove(updated)
332
+ if (!extractedBlock) return
333
+
334
+ const sectionHeight = getMaxSubtreeSectionHeight(extractedBlock)
335
+ if (sectionHeight > 0) {
336
+ let targetBaseSectionDepth = 0
337
+ if (targetParentId) {
338
+ const parentInfo = findBlockInfo(cleanedTree, targetParentId, 0)
339
+ if (parentInfo) {
340
+ const isParentSection = parentInfo.block.type === "SectionBlock"
341
+ targetBaseSectionDepth = isParentSection ? parentInfo.depth + 1 : parentInfo.depth
342
+ }
343
+ }
344
+ const isMovingSection = (extractedBlock as BaseBlock).type === "SectionBlock"
345
+ const maxResultDepth = isMovingSection
346
+ ? targetBaseSectionDepth + sectionHeight - 1
347
+ : targetBaseSectionDepth + sectionHeight
348
+ if (maxResultDepth > MAX_SECTION_DEPTH) {
349
+ toast.error(
350
+ "Invalid Section Nesting",
351
+ "Cannot move section here because it would exceed the maximum heading depth (H6)."
352
+ )
353
+ return
354
+ }
355
+ }
356
+
357
+ if (!targetParentId) {
358
+ const idx = Math.min(Math.max(0, newIndex), cleanedTree.length)
359
+ cleanedTree.splice(idx, 0, extractedBlock)
360
+ } else {
361
+ const insertIntoTarget = (list: BaseBlock[]): boolean => {
362
+ for (const item of list) {
363
+ if (item.id === targetParentId) {
364
+ const children = item.children || item.props?.children || []
365
+ const idx = Math.min(Math.max(0, newIndex), children.length)
366
+ children.splice(idx, 0, extractedBlock!)
367
+ item.children = children
368
+ item.props.children = children
369
+ return true
370
+ }
371
+ const sub = item.children || item.props?.children
372
+ if (sub && insertIntoTarget(sub)) return true
373
+ }
374
+ return false
375
+ }
376
+ if (!insertIntoTarget(cleanedTree)) {
377
+ const idx = Math.min(Math.max(0, newIndex), cleanedTree.length)
378
+ cleanedTree.splice(idx, 0, extractedBlock)
379
+ }
380
+ }
381
+
382
+ setBlocksStore(reconcile(cleanedTree))
383
+ notifyChange(true)
384
+ }
385
+
386
+ const saveDraft = () => {
387
+ autosaveController.notifyChange({ blocks: JSON.parse(JSON.stringify(blocks)) })
388
+ }
389
+
390
+ return {
391
+ blocks,
392
+ status,
393
+ previewVisible,
394
+ setPreviewVisible,
395
+ pickerTarget,
396
+ setBlocks,
397
+ updateBlockProps,
398
+ undo,
399
+ redo,
400
+ deleteBlock,
401
+ moveBlock,
402
+ duplicateBlock,
403
+ insertBlock,
404
+ relocateBlock,
405
+ openPicker,
406
+ closePicker,
407
+ saveDraft
408
+ }
409
+ }
410
+
411
+ export type EditorState = ReturnType<typeof createEditorState>
@@ -0,0 +1,251 @@
1
+ import type { BaseBlock } from "../../core/types/blocks"
2
+
3
+ export class HistoryStack {
4
+ private undoStack: BaseBlock[][] = []
5
+ private redoStack: BaseBlock[][] = []
6
+ private debounceTimer: number | null = null
7
+ private maxDepth: number
8
+ private paused = false
9
+
10
+ constructor(initialBlocks: BaseBlock[] = [], maxDepth = 100) {
11
+ this.maxDepth = maxDepth
12
+ this.undoStack.push(JSON.parse(JSON.stringify(initialBlocks)))
13
+ }
14
+
15
+ push(blocks: BaseBlock[], immediate = false): void {
16
+ if (this.paused) return
17
+ const clone = JSON.parse(JSON.stringify(blocks))
18
+
19
+ if (immediate) {
20
+ this.doPush(clone)
21
+ return
22
+ }
23
+
24
+ if (this.debounceTimer !== null) {
25
+ clearTimeout(this.debounceTimer)
26
+ }
27
+
28
+ this.debounceTimer = window.setTimeout(() => {
29
+ this.doPush(clone)
30
+ this.debounceTimer = null
31
+ }, 500)
32
+ }
33
+
34
+ private doPush(snapshot: BaseBlock[]): void {
35
+ if (this.undoStack.length >= this.maxDepth) {
36
+ this.undoStack.shift()
37
+ }
38
+ this.undoStack.push(snapshot)
39
+ this.redoStack = []
40
+ }
41
+
42
+ undo(): BaseBlock[] | null {
43
+ if (this.undoStack.length <= 1) return null
44
+ const current = this.undoStack.pop()!
45
+ this.redoStack.push(current)
46
+ const previous = this.undoStack[this.undoStack.length - 1]
47
+ return JSON.parse(JSON.stringify(previous))
48
+ }
49
+
50
+ redo(): BaseBlock[] | null {
51
+ if (this.redoStack.length === 0) return null
52
+ const next = this.redoStack.pop()!
53
+ this.undoStack.push(next)
54
+ return JSON.parse(JSON.stringify(next))
55
+ }
56
+
57
+ /**
58
+ * Suppress snapshot captures (e.g. during programmatic block loads)
59
+ */
60
+ pause(): void {
61
+ this.paused = true
62
+ if (this.debounceTimer !== null) {
63
+ clearTimeout(this.debounceTimer)
64
+ this.debounceTimer = null
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Resume snapshot captures
70
+ */
71
+ resume(): void {
72
+ this.paused = false
73
+ }
74
+
75
+ /**
76
+ * Returns an immutable deep-clone of the current state for persistence
77
+ */
78
+ commit(blocks: BaseBlock[]): BaseBlock[] {
79
+ return JSON.parse(JSON.stringify(blocks))
80
+ }
81
+
82
+ canUndo(): boolean {
83
+ return this.undoStack.length > 1
84
+ }
85
+
86
+ canRedo(): boolean {
87
+ return this.redoStack.length > 0
88
+ }
89
+
90
+ reset(blocks: BaseBlock[]): void {
91
+ this.undoStack = [JSON.parse(JSON.stringify(blocks))]
92
+ this.redoStack = []
93
+ this.paused = false
94
+ if (this.debounceTimer !== null) {
95
+ clearTimeout(this.debounceTimer)
96
+ this.debounceTimer = null
97
+ }
98
+ }
99
+ }
100
+
101
+ // ─── Default block factory (shared across all editors) ────────────────────────
102
+
103
+ export function createDefaultBlock(type: string): BaseBlock {
104
+ const id =
105
+ typeof crypto !== "undefined" && crypto.randomUUID
106
+ ? crypto.randomUUID()
107
+ : Math.random().toString(36).slice(2, 11)
108
+
109
+ switch (type) {
110
+ case "SectionBlock":
111
+ return {
112
+ id,
113
+ type,
114
+ children: [],
115
+ props: { level: 2, title: "New Section", description: "", children: [] }
116
+ }
117
+ case "ParagraphBlock":
118
+ return { id, type, props: { text: [] } }
119
+ case "CalloutBlock":
120
+ return { id, type, children: [], props: { variant: "info", children: [] } }
121
+ case "ImageBlock":
122
+ return { id, type, props: { src: "", alt: "", caption: "" } }
123
+ case "ScriptBlock":
124
+ return {
125
+ id,
126
+ type,
127
+ children: [],
128
+ props: {
129
+ title: "New Script",
130
+ synopsis: "",
131
+ characters: [],
132
+ aPlot: "",
133
+ bPlot: "",
134
+ cPlot: "",
135
+ stinger: "",
136
+ children: []
137
+ }
138
+ }
139
+ case "SceneBlock":
140
+ return {
141
+ id,
142
+ type,
143
+ children: [],
144
+ props: {
145
+ location: "",
146
+ timeOfDay: "OTHER",
147
+ setting: "OTHER",
148
+ transition: "NONE",
149
+ description: "",
150
+ children: []
151
+ }
152
+ }
153
+ case "DialogueBlock":
154
+ return { id, type, props: { character: "", parenthetical: "", line: "" } }
155
+ case "CodeBlock":
156
+ return { id, type, props: { code: "", language: "typescript", caption: "" } }
157
+ case "TableBlock":
158
+ return {
159
+ id,
160
+ type,
161
+ props: {
162
+ columns: [
163
+ { key: "col1", header: "Header 1", align: "left" },
164
+ { key: "col2", header: "Header 2", align: "left" }
165
+ ],
166
+ rows: [{ col1: "Cell 1", col2: "Cell 2" }],
167
+ caption: ""
168
+ }
169
+ }
170
+ case "TabsBlock":
171
+ return {
172
+ id,
173
+ type,
174
+ children: [],
175
+ props: {
176
+ defaultValue: "tab-1",
177
+ children: []
178
+ }
179
+ }
180
+ case "TabItemBlock":
181
+ return {
182
+ id,
183
+ type,
184
+ children: [],
185
+ props: {
186
+ label: "Tab 1",
187
+ value: "tab-1",
188
+ children: []
189
+ }
190
+ }
191
+ case "StepsBlock":
192
+ return {
193
+ id,
194
+ type,
195
+ children: [],
196
+ props: {
197
+ children: []
198
+ }
199
+ }
200
+ case "StepItemBlock":
201
+ return {
202
+ id,
203
+ type,
204
+ children: [],
205
+ props: {
206
+ title: "Step 1",
207
+ description: "",
208
+ children: []
209
+ }
210
+ }
211
+ case "CardsBlock":
212
+ return {
213
+ id,
214
+ type,
215
+ children: [],
216
+ props: {
217
+ columns: 2,
218
+ children: []
219
+ }
220
+ }
221
+ case "CardBlock":
222
+ return {
223
+ id,
224
+ type,
225
+ props: {
226
+ title: "Card Title",
227
+ description: "Card description text",
228
+ icon: "i-lucide-book-open",
229
+ href: "",
230
+ badge: ""
231
+ }
232
+ }
233
+ case "FileTreeBlock":
234
+ return {
235
+ id,
236
+ type,
237
+ props: {
238
+ tree: [
239
+ {
240
+ name: "src",
241
+ isDir: true,
242
+ children: [{ name: "index.ts", isDir: false }]
243
+ },
244
+ { name: "package.json", isDir: false }
245
+ ]
246
+ }
247
+ }
248
+ default:
249
+ return { id, type, props: { text: [] } }
250
+ }
251
+ }