@alstar/studio 0.0.0-beta.5 → 0.0.0-beta.7

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 (53) hide show
  1. package/api/api-key.ts +74 -0
  2. package/api/block.ts +82 -42
  3. package/api/index.ts +9 -1
  4. package/api/mcp.ts +53 -0
  5. package/components/AdminPanel.ts +74 -0
  6. package/components/BlockFieldRenderer.ts +121 -0
  7. package/components/BlockRenderer.ts +22 -0
  8. package/components/Entries.ts +5 -4
  9. package/components/Entry.ts +13 -21
  10. package/components/FieldRenderer.ts +35 -0
  11. package/components/Render.ts +46 -0
  12. package/components/Settings.ts +98 -0
  13. package/components/{layout.ts → SiteLayout.ts} +9 -13
  14. package/components/fields/Markdown.ts +44 -0
  15. package/components/fields/Text.ts +42 -0
  16. package/components/fields/index.ts +4 -0
  17. package/components/icons.ts +59 -0
  18. package/index.ts +52 -30
  19. package/package.json +3 -2
  20. package/pages/entry/[id].ts +17 -0
  21. package/{components → pages}/index.ts +7 -4
  22. package/pages/settings.ts +10 -0
  23. package/public/studio/admin-panel.css +103 -0
  24. package/public/studio/blocks.css +53 -0
  25. package/public/studio/main.css +162 -0
  26. package/public/studio/main.js +10 -0
  27. package/public/studio/markdown-editor.js +34 -0
  28. package/public/studio/settings.css +24 -0
  29. package/public/studio/sortable-list.js +40 -0
  30. package/queries/block-with-children.ts +74 -0
  31. package/queries/block.ts +31 -40
  32. package/queries/db-types.ts +15 -0
  33. package/queries/getBlockTrees-2.ts +0 -0
  34. package/queries/getBlockTrees.ts +316 -0
  35. package/queries/getBlocks.ts +214 -0
  36. package/queries/index.ts +1 -0
  37. package/queries/structure-types.ts +97 -0
  38. package/schemas.ts +14 -3
  39. package/types.ts +123 -6
  40. package/utils/buildBlocksTree.ts +4 -4
  41. package/utils/define.ts +31 -5
  42. package/utils/file-based-router.ts +1 -0
  43. package/utils/get-or-create-row.ts +41 -0
  44. package/utils/startup-log.ts +9 -0
  45. package/components/AdminPanel/AdminPanel.css +0 -78
  46. package/components/AdminPanel/AdminPanel.ts +0 -57
  47. package/components/Block.ts +0 -116
  48. package/components/Field.ts +0 -168
  49. package/components/Fields.ts +0 -43
  50. package/public/main.css +0 -95
  51. package/public/main.js +0 -44
  52. /package/{components/Entry.css → public/studio/entry.css} +0 -0
  53. /package/public/{favicon.svg → studio/favicon.svg} +0 -0
package/api/api-key.ts ADDED
@@ -0,0 +1,74 @@
1
+ import { type HttpBindings } from '@hono/node-server'
2
+ import { ServerSentEventGenerator } from '@starfederation/datastar-sdk'
3
+ import { Hono } from 'hono'
4
+ import { streamSSE } from 'hono/streaming'
5
+ import { db } from '@alstar/db'
6
+ import crypto from 'node:crypto'
7
+
8
+ import { stripNewlines } from '../utils/strip-newlines.ts'
9
+ import { sql } from '../utils/sql.ts'
10
+ import { type Structure } from '../types.ts'
11
+ import Settings from '../components/Settings.ts'
12
+
13
+ export default (structure: Structure) => {
14
+ const app = new Hono<{ Bindings: HttpBindings }>()
15
+
16
+ app.post('/api-key', async (c) => {
17
+ return streamSSE(c, async (stream) => {
18
+ const formData = await c.req.formData()
19
+ const data = Object.fromEntries(formData.entries())
20
+
21
+ if (!data) return
22
+
23
+ const apiKey = crypto.randomUUID()
24
+ const hash = crypto.createHash('sha256')
25
+
26
+ hash.update(apiKey)
27
+
28
+ const digest = hash.digest().toString('base64')
29
+
30
+ const xs = (length: number) => '*'.repeat(length)
31
+
32
+ db.insertInto('api_keys', {
33
+ name: data.name?.toString(),
34
+ value: digest,
35
+ hint: `${apiKey.substring(0, 8)}-${xs(4)}-${xs(4)}-${xs(4)}-${xs(12)}`,
36
+ })
37
+
38
+ await stream.writeSSE({
39
+ event: 'datastar-patch-signals',
40
+ data: `signals { apiKey: '${apiKey}', name: '' }`,
41
+ })
42
+
43
+ await stream.writeSSE({
44
+ event: 'datastar-patch-elements',
45
+ data: `elements ${stripNewlines(Settings())}`,
46
+ })
47
+ })
48
+ })
49
+
50
+ app.delete('/api-key', async (c) => {
51
+ return streamSSE(c, async (stream) => {
52
+ const formData = await c.req.formData()
53
+
54
+ const value = formData.get('value')?.toString()
55
+
56
+ if (!value) return
57
+
58
+ db.database
59
+ .prepare(sql`
60
+ delete from api_keys
61
+ where
62
+ value = ?
63
+ `)
64
+ .run(value)
65
+
66
+ await stream.writeSSE({
67
+ event: 'datastar-patch-elements',
68
+ data: `elements ${stripNewlines(Settings())}`,
69
+ })
70
+ })
71
+ })
72
+
73
+ return app
74
+ }
package/api/block.ts CHANGED
@@ -6,10 +6,15 @@ import { stripNewlines } from '../utils/strip-newlines.ts'
6
6
  import { sql } from '../utils/sql.ts'
7
7
  import { type Structure } from '../types.ts'
8
8
  import { db } from '@alstar/db'
9
- import Entries from '../components/Entries.ts'
10
9
  import Entry from '../components/Entry.ts'
11
-
12
- export const sectionRoutes = (structure: Structure) => {
10
+ import {
11
+ blockWithChildren,
12
+ deleteBlockWithChildren,
13
+ } from '../queries/block-with-children.ts'
14
+ import AdminPanel from '../components/AdminPanel.ts'
15
+ import { query } from '../queries/index.ts'
16
+
17
+ export default (structure: Structure) => {
13
18
  const app = new Hono<{ Bindings: HttpBindings }>()
14
19
 
15
20
  app.post('/block', async (c) => {
@@ -17,19 +22,19 @@ export const sectionRoutes = (structure: Structure) => {
17
22
  const formData = await c.req.formData()
18
23
  const data = Object.fromEntries(formData.entries())
19
24
 
20
- const row = structure.find((block) => block.type === data.type)
25
+ const row = structure[data.name?.toString()]
21
26
 
22
27
  if (!row) return
23
28
 
24
29
  db.insertInto('blocks', {
25
- name: row.name?.toString(),
26
- label: row.label?.toString(),
27
- type: row.type?.toString(),
30
+ name: data.name?.toString(),
31
+ label: row.label,
32
+ type: row.type,
28
33
  })
29
34
 
30
35
  await stream.writeSSE({
31
36
  event: 'datastar-patch-elements',
32
- data: `elements ${stripNewlines(Entries())}`,
37
+ data: `elements ${stripNewlines(AdminPanel())}`,
33
38
  })
34
39
  })
35
40
  })
@@ -37,26 +42,19 @@ export const sectionRoutes = (structure: Structure) => {
37
42
  app.post('/new-block', async (c) => {
38
43
  return streamSSE(c, async (stream) => {
39
44
  const formData = await c.req.formData()
40
- const newBlock = formData.get('block')?.toString().split(';')
41
- const columns = newBlock?.map((field) => field.split(':')) as string[][]
42
- const data = Object.fromEntries(columns)
43
-
44
- const parent_block_id = formData?.get('parent_block_id')?.toString()
45
- const sort_order = formData?.get('sort_order')?.toString()
46
-
47
- if (!parent_block_id || !sort_order) return
45
+ const data = Object.fromEntries(formData)
48
46
 
49
47
  db.insertInto('blocks', {
50
- type: data.type,
51
- name: data.name,
52
- label: data.label,
53
- parent_block_id,
54
- sort_order,
48
+ type: data.type.toString(),
49
+ name: data.name.toString(),
50
+ label: data.label.toString(),
51
+ parent_id: data.parent_id.toString(),
52
+ sort_order: data.sort_order.toString(),
55
53
  })
56
54
 
57
55
  await stream.writeSSE({
58
56
  event: 'datastar-patch-elements',
59
- data: `elements ${stripNewlines(Entry({ entryId: data.entry_id, structure }))}`,
57
+ data: `elements ${stripNewlines(Entry({ entryId: parseInt(data.entry_id.toString()) }))}`,
60
58
  })
61
59
  })
62
60
  })
@@ -70,54 +68,96 @@ export const sectionRoutes = (structure: Structure) => {
70
68
  const name = formData.get('name')?.toString()
71
69
  const entryId = formData.get('entryId')?.toString()
72
70
  const parentId = formData.get('parentId')?.toString()
73
- const sortOrder = formData.get('sort_order')?.toString()
71
+ // const sortOrder = formData.get('sort_order')?.toString()
74
72
 
75
- if (!id || !value || !sortOrder) return
73
+ if (!id || !value) return
76
74
 
77
75
  const transaction = db.database.prepare(sql`
78
76
  update blocks
79
77
  set
80
78
  value = ?
81
79
  where
82
- id = ?
83
- and sort_order = ?;
80
+ id = ?;
84
81
  `)
85
82
 
86
- transaction.run(value, id, sortOrder)
83
+ transaction.run(value, id)
87
84
 
88
85
  if (entryId === parentId && name?.toString() === 'title') {
89
- await stream.writeSSE({
90
- event: 'datastar-patch-elements',
91
- data: `elements <a href="/admin/entry/${entryId}" id="block_link_${entryId}">${value}</a>`,
86
+ const rootBlock = query.block({
87
+ id: parentId?.toString() || null,
92
88
  })
89
+
90
+ if (rootBlock.type !== 'single') {
91
+ await stream.writeSSE({
92
+ event: 'datastar-patch-elements',
93
+ data: `elements <a href="/admin/entry/${entryId}" id="block_link_${entryId}">${value}</a>`,
94
+ })
95
+ }
93
96
  }
94
97
  })
95
98
  })
96
99
 
100
+ app.patch('/value', async (c) => {
101
+ const body = await c.req.json()
102
+
103
+ const transaction = db.database.prepare(sql`
104
+ update blocks
105
+ set
106
+ value = ?
107
+ where
108
+ id = ?;
109
+ `)
110
+
111
+ transaction.run(body.value, body.id)
112
+
113
+ return c.json({ status: 200, message: 'success' })
114
+ })
115
+
97
116
  app.delete('/block', async (c) => {
98
117
  return streamSSE(c, async (stream) => {
99
118
  const formData = await c.req.formData()
100
119
 
101
120
  const id = formData.get('id')?.toString()
121
+ const entryId = formData.get('entry_id')?.toString()
102
122
 
103
123
  if (!id) return
104
124
 
105
- const transaction = db.database.prepare(sql`
106
- update blocks
107
- set
108
- status = 'disabled'
109
- where
110
- id = ?
111
- `)
125
+ const transaction = db.database.prepare(deleteBlockWithChildren)
112
126
 
113
- transaction.run(id)
127
+ transaction.all(id)
114
128
 
115
- await stream.writeSSE({
116
- event: 'datastar-patch-elements',
117
- data: `elements ${stripNewlines(Entries())}`,
118
- })
129
+ if (entryId) {
130
+ await stream.writeSSE({
131
+ event: 'datastar-patch-elements',
132
+ data: `elements ${stripNewlines(Entry({ entryId: parseInt(entryId.toString()) }))}`,
133
+ })
134
+ } else {
135
+ await stream.writeSSE({
136
+ event: 'datastar-patch-elements',
137
+ data: `elements ${stripNewlines(AdminPanel())}`,
138
+ })
139
+ }
119
140
  })
120
141
  })
121
142
 
143
+ app.post('/sort-order', async (c) => {
144
+ const id = c.req.query('id')
145
+ const sortOrder = c.req.query('sort-order')
146
+
147
+ if (!id || !sortOrder) return
148
+
149
+ const transaction = db.database.prepare(sql`
150
+ update blocks
151
+ set
152
+ sort_order = ?
153
+ where
154
+ id = ?
155
+ `)
156
+
157
+ transaction.run(sortOrder, id)
158
+
159
+ return c.json({ status: 200, message: 'success' })
160
+ })
161
+
122
162
  return app
123
163
  }
package/api/index.ts CHANGED
@@ -1,2 +1,10 @@
1
- export * from './block.ts'
1
+ import block from './block.ts'
2
+ import apiKey from './api-key.ts'
2
3
 
4
+ export const api = (structure) => {
5
+ const app = block(structure)
6
+
7
+ app.route('/', apiKey(structure))
8
+
9
+ return app
10
+ }
package/api/mcp.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { type HttpBindings } from '@hono/node-server'
2
+ import { ServerSentEventGenerator } from '@starfederation/datastar-sdk'
3
+ import { Hono } from 'hono'
4
+ import { sql } from '../utils/sql.ts'
5
+ import { db } from '@alstar/db'
6
+ import { bearerAuth } from 'hono/bearer-auth'
7
+ import crypto from 'node:crypto'
8
+
9
+ export default () => {
10
+ const app = new Hono<{ Bindings: HttpBindings }>()
11
+
12
+ app.use(
13
+ '/*',
14
+ bearerAuth({
15
+ verifyToken: async (token, c) => {
16
+ const hash = crypto.createHash('sha256')
17
+
18
+ hash.update(token)
19
+
20
+ const digest = hash.digest().toString('base64')
21
+
22
+ const exists = db.database
23
+ .prepare(sql`
24
+ select
25
+ value
26
+ from
27
+ api_keys
28
+ where
29
+ value = ?
30
+ `)
31
+ .get(digest)
32
+
33
+ return !!exists
34
+ },
35
+ }),
36
+ )
37
+
38
+ app.get('/entry', async (c) => {
39
+ return c.json({
40
+ status: 'success',
41
+ message: 'Response from MCP server!',
42
+ })
43
+ })
44
+
45
+ app.post('/entry', async (c) => {
46
+ return c.json({
47
+ status: 'success',
48
+ message: 'New entry created!',
49
+ })
50
+ })
51
+
52
+ return app
53
+ }
@@ -0,0 +1,74 @@
1
+ import { html } from 'hono/html'
2
+ import { logo } from './icons.ts'
3
+ import Entries from './Entries.ts'
4
+ import * as icons from './icons.ts'
5
+ import { studioStructure } from '../index.ts'
6
+ import { getOrCreateRow } from '../utils/get-or-create-row.ts'
7
+
8
+ export default () => {
9
+ const entries = Object.entries(studioStructure)
10
+
11
+ return html`
12
+ <div class="admin-panel" id="admin_panel">
13
+ <h1>
14
+ <a href="/admin" aria-label="Go to dashboard"> ${logo} </a>
15
+ </h1>
16
+
17
+ <aside style="width: 100%;">
18
+ ${entries.map(([name, block]) => {
19
+ if (block.type === 'single') {
20
+ const data = getOrCreateRow({ parentId: null, name, field: block })
21
+
22
+ return html`
23
+ <section id="entries">
24
+ <ul>
25
+ <li>
26
+ <a
27
+ href="/admin/entry/${data.id}"
28
+ id="block_link_${data.id}"
29
+ >
30
+ ${block.label}
31
+ </a>
32
+ </li>
33
+ </ul>
34
+ </section>
35
+ `
36
+ }
37
+
38
+ return html`
39
+ <form
40
+ data-on-submit="@post('/admin/api/block', { contentType: 'form' })"
41
+ style="display: flex; align-items: center; gap: 1rem;"
42
+ >
43
+ <input type="hidden" name="name" value="${name}" />
44
+ <button
45
+ class="ghost"
46
+ style="padding: 10px; margin: 0 -13px; display: flex;"
47
+ data-tooltip="New ${block.label}"
48
+ data-placement="right"
49
+ >
50
+ ${icons.newDocument}
51
+ </button>
52
+ <p style="user-select: none;"><small>${block.label}</small></p>
53
+ </form>
54
+
55
+ ${Entries({ name })}
56
+ `
57
+ })}
58
+ </aside>
59
+
60
+ <footer>
61
+ <a
62
+ role="button"
63
+ href="/admin/settings"
64
+ class="ghost"
65
+ style="padding: 10px; margin: 0 -13px; display: flex;"
66
+ data-tooltip="Settings"
67
+ data-placement="right"
68
+ >
69
+ ${icons.cog}
70
+ </a>
71
+ </footer>
72
+ </div>
73
+ `
74
+ }
@@ -0,0 +1,121 @@
1
+ import { query } from '../queries/index.ts'
2
+ import type { BlocksFieldDefStructure } from '../types.ts'
3
+ import { BlockInstance } from '../utils/define.ts'
4
+ import { getOrCreateRow } from '../utils/get-or-create-row.ts'
5
+ import { html } from '../utils/html.ts'
6
+ import * as icons from './icons.ts'
7
+ import Render from './Render.ts'
8
+
9
+ export default (props: {
10
+ entryId: number
11
+ parentId: number
12
+ name: string
13
+ structure: BlocksFieldDefStructure
14
+ id?: number
15
+ sortOrder?: number
16
+ }) => {
17
+ const { entryId, parentId, name, structure, id, sortOrder = 0 } = props
18
+
19
+ const data = getOrCreateRow({
20
+ parentId,
21
+ name,
22
+ field: structure,
23
+ sortOrder,
24
+ id,
25
+ })
26
+
27
+ if (!data) return html`<p>No block</p>`
28
+
29
+ const entries = Object.entries(structure.children)
30
+
31
+ const rows = query.blocks({ parent_id: data.id })
32
+
33
+ return html`
34
+ <div class="blocks-field">
35
+ <header>
36
+ <p>${structure.label}</p>
37
+
38
+ <details class="dropdown">
39
+ <summary>Add</summary>
40
+ <ul>
41
+ ${entries.map(([name, block]) => {
42
+ return html`
43
+ <li>
44
+ <form
45
+ data-on-submit="@post('/admin/api/new-block', { contentType: 'form' })"
46
+ >
47
+ <button type="submit" class="ghost">${block.label}</button>
48
+ <input type="hidden" name="type" value="${block.type}" />
49
+ <input type="hidden" name="name" value="${name}" />
50
+ <input type="hidden" name="label" value="${block.label}" />
51
+ <input type="hidden" name="parent_id" value="${data.id}" />
52
+ <input type="hidden" name="entry_id" value="${entryId}" />
53
+ <input
54
+ type="hidden"
55
+ name="sort_order"
56
+ value="${rows.length}"
57
+ />
58
+ </form>
59
+ </li>
60
+ `
61
+ })}
62
+ </ul>
63
+ </details>
64
+ </header>
65
+
66
+ <hr style="margin-top: 0;" />
67
+
68
+ <sortable-list data-id="${data.id}">
69
+ ${rows.map((row, idx) => {
70
+ const [name, struct] =
71
+ entries.find(([name]) => name === row.name) || []
72
+
73
+ if (!name || !struct) return html`<p>No name</p>`
74
+
75
+ return html`
76
+ <article data-id="${row.id}">
77
+ <header>
78
+ ${struct.label}
79
+
80
+ <aside>
81
+ <button
82
+ data-handle-for="${data.id}"
83
+ class="ghost handle text-secondary"
84
+ style="cursor: grab"
85
+ >
86
+ ${icons.bars}
87
+ </button>
88
+
89
+ <form
90
+ data-on-submit="@delete('/admin/api/block', { contentType: 'form' })"
91
+ >
92
+ <button
93
+ type="submit"
94
+ class="ghost text-secondary"
95
+ data-tooltip="Remove"
96
+ data-placement="top"
97
+ aria-label="Delete block"
98
+ >
99
+ ${icons.x}
100
+ </button>
101
+ <input type="hidden" name="id" value="${row.id}" />
102
+ <input type="hidden" name="entry_id" value="${entryId}" />
103
+ </form>
104
+ </aside>
105
+ </header>
106
+
107
+ ${Render({
108
+ entryId,
109
+ parentId:
110
+ struct.instanceOf === BlockInstance ? row.id : data.id,
111
+ id: row.id,
112
+ structure: struct,
113
+ name: name,
114
+ })}
115
+ </article>
116
+ `
117
+ })}
118
+ </sortable-list>
119
+ </div>
120
+ `
121
+ }
@@ -0,0 +1,22 @@
1
+ import { html } from 'hono/html'
2
+ import type { BlockDef } from '../types.ts'
3
+ import Render from './Render.ts'
4
+
5
+ export default (props: {
6
+ entryId: number
7
+ parentId: number
8
+ id?: number
9
+ structure: BlockDef
10
+ }) => {
11
+ const { entryId, parentId, structure, id } = props
12
+
13
+ const entries = Object.entries(structure.fields)
14
+
15
+ return entries.map(([name, field]) => {
16
+ try {
17
+ return Render({ entryId, parentId, structure: field, name })
18
+ } catch (error) {
19
+ return html`<p>Cound not render: "${name}"</p>`
20
+ }
21
+ }).join('')
22
+ }
@@ -1,16 +1,17 @@
1
1
  import { html } from 'hono/html'
2
2
  import { query } from '../index.ts'
3
3
  import * as icons from './icons.ts'
4
+ import type { BlockDef } from '../types.ts'
4
5
 
5
- export default () => {
6
- const entries = query.blocks({ parent_block_id: null, status: 'enabled' })
6
+ export default ({ name }: { name: string }) => {
7
+ const entries = query.blocks({ parent_id: null, status: 'enabled', name })
7
8
 
8
9
  return html`
9
10
  <section id="entries">
10
11
  <ul>
11
12
  ${entries?.map((block) => {
12
13
  const title = query.block({
13
- parent_block_id: block.id.toString(),
14
+ parent_id: block.id.toString(),
14
15
  name: 'title',
15
16
  })
16
17
 
@@ -27,7 +28,7 @@ export default () => {
27
28
  <button
28
29
  data-tooltip="Remove"
29
30
  data-placement="right"
30
- class="ghost"
31
+ class="ghost text-secondary"
31
32
  style="padding: 0"
32
33
  type="submit"
33
34
  >
@@ -1,34 +1,26 @@
1
1
  import { html } from 'hono/html'
2
2
  import { query } from '../queries/index.ts'
3
- import { rootdir } from '../index.ts'
4
- import { type Structure } from '../types.ts'
5
- import Fields from './Fields.ts'
6
- import { buildStructurePath } from '../utils/build-structure-path.ts'
3
+ import { studioStructure } from '../index.ts'
4
+ import Render from './Render.ts'
7
5
 
8
- export default (props: { entryId: number | string; structure: Structure }) => {
6
+ export default (props: { entryId: number }) => {
9
7
  const data = query.block({ id: props.entryId?.toString() })
10
8
 
11
9
  if (!data) return html`<p>No entry with id: "${props.entryId}"</p>`
12
10
 
13
- const blockStructure = props.structure.find(
14
- (block) => block.name === data.name,
15
- )
11
+ const structure = studioStructure[data.name]
16
12
 
17
- const structurePath = buildStructurePath(blockStructure)
13
+ if (!structure) return html`<p>No structure of type: ${data.name}</p>`
18
14
 
19
15
  return html`
20
- <div id="entry">
21
- <link rel="stylesheet" href="${rootdir}/components/Entry.css" />
22
-
23
- <div class="entry">
24
- ${blockStructure &&
25
- Fields({
26
- entryId: props.entryId,
27
- parentId: props.entryId,
28
- blockStructure,
29
- structurePath,
30
- })}
31
- </div>
16
+ <div id="entry" class="entry">
17
+ ${Render({
18
+ entryId: props.entryId,
19
+ parentId: props.entryId,
20
+ structure: structure,
21
+ name: data.name,
22
+ })}
32
23
  </div>
24
+
33
25
  `
34
26
  }
@@ -0,0 +1,35 @@
1
+ import { Field } from './fields/index.ts'
2
+ import type { BlocksFieldDefStructure, FieldDefStructure } from '../types.ts'
3
+ import BlockFieldRenderer from './BlockFieldRenderer.ts'
4
+
5
+ export default (props: {
6
+ entryId: number
7
+ parentId: number
8
+ structure: FieldDefStructure | BlocksFieldDefStructure
9
+ id?: number
10
+ name: string
11
+ }) => {
12
+ const { entryId, parentId, structure, name, id } = props
13
+
14
+ switch (structure.type) {
15
+ case 'text': {
16
+ return Field.Text({ entryId, parentId, name, id, structure })
17
+ }
18
+
19
+ case 'slug': {
20
+ return Field.Text({ entryId, parentId, name, id, structure })
21
+ }
22
+
23
+ case 'markdown': {
24
+ return Field.Markdown({ entryId, parentId, name, id, structure })
25
+ }
26
+
27
+ case 'image': {
28
+ return Field.Text({ entryId, parentId, name, structure, id })
29
+ }
30
+
31
+ case 'blocks': {
32
+ return BlockFieldRenderer({ entryId, parentId, name, structure, id })
33
+ }
34
+ }
35
+ }