@alstar/studio 0.0.0-beta.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/api/block.ts +97 -0
- package/api/index.ts +2 -0
- package/components/AdminPanel/AdminPanel.css +59 -0
- package/components/AdminPanel/AdminPanel.ts +57 -0
- package/components/Block.ts +116 -0
- package/components/Entries.ts +37 -0
- package/components/Entry.css +7 -0
- package/components/Entry.ts +34 -0
- package/components/Field.ts +164 -0
- package/components/Fields.ts +43 -0
- package/components/icons.ts +103 -0
- package/components/index.ts +30 -0
- package/components/layout.ts +53 -0
- package/index.ts +71 -0
- package/package.json +39 -0
- package/public/favicon.svg +6 -0
- package/public/main.css +92 -0
- package/public/main.js +43 -0
- package/queries/index.ts +98 -0
- package/schemas.ts +67 -0
- package/tsconfig.json +3 -0
- package/types.ts +25 -0
- package/utils/build-structure-path.ts +43 -0
- package/utils/buildBlocksTree.ts +44 -0
- package/utils/create-studio-tables.ts +8 -0
- package/utils/file-based-router.ts +58 -0
- package/utils/get-config.ts +26 -0
- package/utils/slugify.ts +10 -0
- package/utils/sql.ts +1 -0
- package/utils/strip-newlines.ts +5 -0
package/api/block.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
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 { stripNewlines } from '../utils/strip-newlines.ts'
|
|
6
|
+
import { sql } from '../utils/sql.ts'
|
|
7
|
+
import { type Structure } from '../types.ts'
|
|
8
|
+
import { db } from '@alstar/db'
|
|
9
|
+
import Entries from '../components/Entries.ts'
|
|
10
|
+
import Entry from '../components/Entry.ts'
|
|
11
|
+
|
|
12
|
+
export const sectionRoutes = (structure: Structure) => {
|
|
13
|
+
const app = new Hono<{ Bindings: HttpBindings }>()
|
|
14
|
+
|
|
15
|
+
app.post('/block', async (c) => {
|
|
16
|
+
return streamSSE(c, async (stream) => {
|
|
17
|
+
const formData = await c.req.formData()
|
|
18
|
+
const data = Object.fromEntries(formData.entries())
|
|
19
|
+
|
|
20
|
+
const row = structure.find((block) => block.type === data.type)
|
|
21
|
+
|
|
22
|
+
if (!row) return
|
|
23
|
+
|
|
24
|
+
db.insertInto('blocks', {
|
|
25
|
+
name: row.name?.toString(),
|
|
26
|
+
label: row.label?.toString(),
|
|
27
|
+
type: row.type?.toString(),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
await stream.writeSSE({
|
|
31
|
+
event: 'datastar-patch-elements',
|
|
32
|
+
data: `elements ${stripNewlines(Entries())}`,
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
app.post('/new-block', async (c) => {
|
|
38
|
+
return streamSSE(c, async (stream) => {
|
|
39
|
+
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
|
|
48
|
+
|
|
49
|
+
db.insertInto('blocks', {
|
|
50
|
+
type: data.type,
|
|
51
|
+
name: data.name,
|
|
52
|
+
label: data.label,
|
|
53
|
+
parent_block_id,
|
|
54
|
+
sort_order,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
await stream.writeSSE({
|
|
58
|
+
event: 'datastar-patch-elements',
|
|
59
|
+
data: `elements ${stripNewlines(Entry({ entryId: data.entry_id, structure }))}`,
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
app.patch('/block', async (c) => {
|
|
65
|
+
return streamSSE(c, async (stream) => {
|
|
66
|
+
const formData = await c.req.formData()
|
|
67
|
+
|
|
68
|
+
const id = formData.get('id')?.toString()
|
|
69
|
+
const value = formData.get('value')?.toString()
|
|
70
|
+
const entryId = formData.get('entryId')?.toString()
|
|
71
|
+
const parentId = formData.get('parentId')?.toString()
|
|
72
|
+
const sortOrder = formData.get('sort_order')?.toString()
|
|
73
|
+
|
|
74
|
+
if (!id || !value || !sortOrder) return
|
|
75
|
+
|
|
76
|
+
const transaction = db.database.prepare(sql`
|
|
77
|
+
update blocks
|
|
78
|
+
set
|
|
79
|
+
value = ?
|
|
80
|
+
where
|
|
81
|
+
id = ?
|
|
82
|
+
and sort_order = ?;
|
|
83
|
+
`)
|
|
84
|
+
|
|
85
|
+
transaction.run(value, id, sortOrder)
|
|
86
|
+
|
|
87
|
+
if (entryId === parentId) {
|
|
88
|
+
await stream.writeSSE({
|
|
89
|
+
event: 'datastar-patch-elements',
|
|
90
|
+
data: `elements <a href="/admin/entry/${entryId}" id="block_link_${entryId}">${value}</a>`,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return app
|
|
97
|
+
}
|
package/api/index.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
.admin-panel {
|
|
2
|
+
/* background: hsla(0, 0%, 0%, 0.1); */
|
|
3
|
+
padding: 40px;
|
|
4
|
+
padding-right: 0;
|
|
5
|
+
|
|
6
|
+
height: 100%;
|
|
7
|
+
min-height: inherit;
|
|
8
|
+
|
|
9
|
+
min-width: 200px;
|
|
10
|
+
|
|
11
|
+
> h1 {
|
|
12
|
+
padding-bottom: 1rem;
|
|
13
|
+
|
|
14
|
+
a {
|
|
15
|
+
display: flex;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
svg {
|
|
19
|
+
height: 1.6rem;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
form {
|
|
24
|
+
padding-bottom: 1rem;
|
|
25
|
+
|
|
26
|
+
button {
|
|
27
|
+
margin: 10px 0px 20px;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#entries ul {
|
|
32
|
+
padding: 0;
|
|
33
|
+
|
|
34
|
+
> li {
|
|
35
|
+
margin-bottom: 0px;
|
|
36
|
+
border-radius: 8px;
|
|
37
|
+
display: flex;
|
|
38
|
+
justify-content: space-between;
|
|
39
|
+
align-items: stretch;
|
|
40
|
+
/* align-items: center; */
|
|
41
|
+
list-style: none;
|
|
42
|
+
margin-inline: -1rem;
|
|
43
|
+
|
|
44
|
+
a {
|
|
45
|
+
text-decoration: none;
|
|
46
|
+
width: 100%;
|
|
47
|
+
padding: 0.5rem 1rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
button {
|
|
51
|
+
border-radius: 7px;
|
|
52
|
+
|
|
53
|
+
svg {
|
|
54
|
+
margin: 0.5rem 1rem;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { html } from 'hono/html'
|
|
2
|
+
import type { Structure } from '../../types.ts'
|
|
3
|
+
import { logo } from '../icons.ts'
|
|
4
|
+
import { rootdir } from '../../index.ts'
|
|
5
|
+
import Entries from '../Entries.ts'
|
|
6
|
+
import * as icons from '../icons.ts'
|
|
7
|
+
|
|
8
|
+
export default (structure: Structure) => {
|
|
9
|
+
return html`
|
|
10
|
+
<link
|
|
11
|
+
rel="stylesheet"
|
|
12
|
+
href="${rootdir}/components/AdminPanel/AdminPanel.css"
|
|
13
|
+
/>
|
|
14
|
+
|
|
15
|
+
<div class="admin-panel">
|
|
16
|
+
<h1>
|
|
17
|
+
<a href="/admin" aria-label="Go to dashboard"> ${logo} </a>
|
|
18
|
+
</h1>
|
|
19
|
+
|
|
20
|
+
<aside>
|
|
21
|
+
<form
|
|
22
|
+
data-on-submit="@post('/admin/api/block', { contentType: 'form' })"
|
|
23
|
+
>
|
|
24
|
+
<!-- <select name="type">
|
|
25
|
+
${structure.map(
|
|
26
|
+
(block) => html`
|
|
27
|
+
<option value="${block.type}">
|
|
28
|
+
${block.label}
|
|
29
|
+
</option>
|
|
30
|
+
`,
|
|
31
|
+
)}
|
|
32
|
+
</select>
|
|
33
|
+
<input type="submit" value="Create" /> -->
|
|
34
|
+
|
|
35
|
+
<!-- TODO: currently only handles a single entry type -->
|
|
36
|
+
${structure.length
|
|
37
|
+
? html`<input
|
|
38
|
+
type="hidden"
|
|
39
|
+
name="type"
|
|
40
|
+
value="${structure[0].type}"
|
|
41
|
+
/>
|
|
42
|
+
<button
|
|
43
|
+
class="ghost"
|
|
44
|
+
style="padding: 10px; margin: 0 -13px; display: flex;"
|
|
45
|
+
data-tooltip="New entry"
|
|
46
|
+
data-placement="right"
|
|
47
|
+
>
|
|
48
|
+
${icons.newDocument}
|
|
49
|
+
</button>`
|
|
50
|
+
: ''}
|
|
51
|
+
</form>
|
|
52
|
+
</aside>
|
|
53
|
+
|
|
54
|
+
${Entries()}
|
|
55
|
+
</div>
|
|
56
|
+
`
|
|
57
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { html } from 'hono/html'
|
|
2
|
+
import { query } from '../queries/index.ts'
|
|
3
|
+
import Fields from './Fields.ts'
|
|
4
|
+
import * as icons from './icons.ts'
|
|
5
|
+
import { type Block } from '../types.ts'
|
|
6
|
+
import Field from './Field.ts'
|
|
7
|
+
import {
|
|
8
|
+
buildStructurePath,
|
|
9
|
+
type StructurePath,
|
|
10
|
+
} from '../utils/build-structure-path.ts'
|
|
11
|
+
|
|
12
|
+
export default (
|
|
13
|
+
entryId: string | number,
|
|
14
|
+
id: string | number,
|
|
15
|
+
fieldStructure: Block,
|
|
16
|
+
structurePath: StructurePath,
|
|
17
|
+
) => {
|
|
18
|
+
const data = query.block({ id: id.toString() })
|
|
19
|
+
const blocks = query.blocks({ parent: id })
|
|
20
|
+
|
|
21
|
+
if (!data) return html`<p>No block data</p>`
|
|
22
|
+
|
|
23
|
+
const fieldStructurePath = buildStructurePath(fieldStructure, structurePath)
|
|
24
|
+
|
|
25
|
+
return html`
|
|
26
|
+
|
|
27
|
+
<section class="block">
|
|
28
|
+
<header>
|
|
29
|
+
<h5>${fieldStructure.label}</h5>
|
|
30
|
+
|
|
31
|
+
<form
|
|
32
|
+
data-on-submit="@post('/admin/api/new-block', { contentType: 'form' })"
|
|
33
|
+
>
|
|
34
|
+
<input type="hidden" name="parent_block_id" value="${id}" />
|
|
35
|
+
<input
|
|
36
|
+
type="hidden"
|
|
37
|
+
name="sort_order"
|
|
38
|
+
value="${blocks?.length || 0}"
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
<fieldset role="group">
|
|
42
|
+
<select name="block">
|
|
43
|
+
${fieldStructure.fields?.map((field) => {
|
|
44
|
+
return html`<option
|
|
45
|
+
value="entry_id:${entryId};type:${field.type};name:${field.name};label:${field.label};sort_order:${blocks?.length ||
|
|
46
|
+
0}"
|
|
47
|
+
>
|
|
48
|
+
${field.label}
|
|
49
|
+
</option>`
|
|
50
|
+
})}
|
|
51
|
+
</select>
|
|
52
|
+
|
|
53
|
+
<button
|
|
54
|
+
class="outline"
|
|
55
|
+
style="padding: 0 1rem"
|
|
56
|
+
data-tooltip="New entry"
|
|
57
|
+
data-placement="right"
|
|
58
|
+
type="submit"
|
|
59
|
+
>
|
|
60
|
+
Add
|
|
61
|
+
</button>
|
|
62
|
+
</fieldset>
|
|
63
|
+
</form>
|
|
64
|
+
</header>
|
|
65
|
+
|
|
66
|
+
<div data-sortable="${id}">
|
|
67
|
+
${blocks?.map((block, idx) => {
|
|
68
|
+
const structure = fieldStructure.fields?.find(
|
|
69
|
+
(field) => field.name === block.name,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if (!structure) return
|
|
73
|
+
|
|
74
|
+
return html`
|
|
75
|
+
<article
|
|
76
|
+
data-signals="{ open${block.id}: true }"
|
|
77
|
+
>
|
|
78
|
+
<header>
|
|
79
|
+
<h6>${structure.label}</h6>
|
|
80
|
+
<div>
|
|
81
|
+
<button
|
|
82
|
+
data-on-click="$open${block.id} = !$open${block.id}"
|
|
83
|
+
style="margin-right: 0.5rem"
|
|
84
|
+
data-style-rotate="$open${block.id} ? '0deg' : '180deg'"
|
|
85
|
+
class="shadow"
|
|
86
|
+
>
|
|
87
|
+
${icons.chevron}
|
|
88
|
+
</button>
|
|
89
|
+
<button class="shadow">${icons.bars}</button>
|
|
90
|
+
</div>
|
|
91
|
+
</header>
|
|
92
|
+
|
|
93
|
+
<div data-show="$open${block.id}">
|
|
94
|
+
${structure.fields
|
|
95
|
+
? Fields({
|
|
96
|
+
entryId,
|
|
97
|
+
parentId: block.id,
|
|
98
|
+
blockStructure: structure,
|
|
99
|
+
structurePath: [...fieldStructurePath, structure.name],
|
|
100
|
+
})
|
|
101
|
+
: Field({
|
|
102
|
+
entryId,
|
|
103
|
+
parentId: block.id,
|
|
104
|
+
blockStructure: structure,
|
|
105
|
+
sortOrder: idx,
|
|
106
|
+
structurePath: [...fieldStructurePath, structure.name],
|
|
107
|
+
})}
|
|
108
|
+
|
|
109
|
+
</div>
|
|
110
|
+
</article>
|
|
111
|
+
`
|
|
112
|
+
})}
|
|
113
|
+
</div>
|
|
114
|
+
</section>
|
|
115
|
+
`
|
|
116
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { html } from 'hono/html'
|
|
2
|
+
import { query } from '../index.ts'
|
|
3
|
+
import * as icons from './icons.ts'
|
|
4
|
+
|
|
5
|
+
export default () => {
|
|
6
|
+
const entries = query.blocks({ parent: null })
|
|
7
|
+
|
|
8
|
+
return html`
|
|
9
|
+
<section id="entries">
|
|
10
|
+
<ul>
|
|
11
|
+
${entries?.map((block) => {
|
|
12
|
+
const title = query.block({
|
|
13
|
+
parent: block.id.toString(),
|
|
14
|
+
name: 'title',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return html`
|
|
18
|
+
<li>
|
|
19
|
+
<a href="/admin/entry/${block.id}" id="block_link_${block.id}">
|
|
20
|
+
${block.value || title?.value || 'Untitled'}
|
|
21
|
+
</a>
|
|
22
|
+
<!-- <button
|
|
23
|
+
data-tooltip="Rename"
|
|
24
|
+
data-placement="right"
|
|
25
|
+
class="ghost"
|
|
26
|
+
style="padding: 0"
|
|
27
|
+
>
|
|
28
|
+
{icons.pen}
|
|
29
|
+
</button> -->
|
|
30
|
+
</li>
|
|
31
|
+
`
|
|
32
|
+
})}
|
|
33
|
+
</ul>
|
|
34
|
+
</section>
|
|
35
|
+
|
|
36
|
+
`
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { html } from 'hono/html'
|
|
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'
|
|
7
|
+
|
|
8
|
+
export default (props: { entryId: number | string; structure: Structure }) => {
|
|
9
|
+
const data = query.block({ id: props.entryId?.toString() })
|
|
10
|
+
|
|
11
|
+
if (!data) return html`<p>No entry with id: "${props.entryId}"</p>`
|
|
12
|
+
|
|
13
|
+
const blockStructure = props.structure.find(
|
|
14
|
+
(block) => block.name === data.name,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
const structurePath = buildStructurePath(blockStructure)
|
|
18
|
+
|
|
19
|
+
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>
|
|
32
|
+
</div>
|
|
33
|
+
`
|
|
34
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { html } from 'hono/html'
|
|
2
|
+
import { type Block } from '../types.ts'
|
|
3
|
+
import { type HtmlEscapedString } from 'hono/utils/html'
|
|
4
|
+
import { query } from '../queries/index.ts'
|
|
5
|
+
import { db } from '@alstar/db'
|
|
6
|
+
import BlockComponent from './Block.ts'
|
|
7
|
+
import {
|
|
8
|
+
buildStructurePath,
|
|
9
|
+
type StructurePath,
|
|
10
|
+
} from '../utils/build-structure-path.ts'
|
|
11
|
+
|
|
12
|
+
function getData(parentId: string | number, field: Block, sortOrder: number) {
|
|
13
|
+
const data = query.block({
|
|
14
|
+
parent: parentId?.toString() || null,
|
|
15
|
+
name: field.name,
|
|
16
|
+
sort_order: sortOrder.toString(),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
if (!data) {
|
|
20
|
+
const change = db.insertInto('blocks', {
|
|
21
|
+
name: field.name?.toString(),
|
|
22
|
+
label: field.label?.toString(),
|
|
23
|
+
type: field.type?.toString(),
|
|
24
|
+
sort_order: sortOrder,
|
|
25
|
+
parent_block_id: parentId,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return query.block({ id: change.lastInsertRowid.toString() })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return data
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const Field = (props: {
|
|
35
|
+
entryId: string | number
|
|
36
|
+
parentId: string | number
|
|
37
|
+
blockStructure: Block
|
|
38
|
+
sortOrder?: number
|
|
39
|
+
structurePath: StructurePath
|
|
40
|
+
}): HtmlEscapedString | Promise<HtmlEscapedString> => {
|
|
41
|
+
const {
|
|
42
|
+
entryId,
|
|
43
|
+
parentId,
|
|
44
|
+
blockStructure,
|
|
45
|
+
sortOrder = 0,
|
|
46
|
+
structurePath,
|
|
47
|
+
} = props
|
|
48
|
+
|
|
49
|
+
const data = getData(parentId, blockStructure, sortOrder)
|
|
50
|
+
|
|
51
|
+
if (!data) return html`<p>No block</p>`
|
|
52
|
+
|
|
53
|
+
const fieldStructurePath = buildStructurePath(blockStructure, structurePath)
|
|
54
|
+
|
|
55
|
+
return html`
|
|
56
|
+
${blockStructure.type === 'slug' &&
|
|
57
|
+
html`
|
|
58
|
+
<form
|
|
59
|
+
data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
|
|
60
|
+
>
|
|
61
|
+
<label for="block-${data.id}">${blockStructure.label}</label>
|
|
62
|
+
<input
|
|
63
|
+
id="block-${data.id}"
|
|
64
|
+
name="value"
|
|
65
|
+
type="text"
|
|
66
|
+
value="${data.value}"
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
<input type="hidden" name="type" value="${blockStructure.type}" />
|
|
70
|
+
<input type="hidden" name="id" value="${data.id}" />
|
|
71
|
+
<input type="hidden" name="entryId" value="${entryId}" />
|
|
72
|
+
<input type="hidden" name="parentId" value="${parentId}" />
|
|
73
|
+
<input type="hidden" name="sort_order" value="${sortOrder}" />
|
|
74
|
+
<input
|
|
75
|
+
type="hidden"
|
|
76
|
+
name="path"
|
|
77
|
+
value="${fieldStructurePath.join('.')}"
|
|
78
|
+
/>
|
|
79
|
+
</form>
|
|
80
|
+
`}
|
|
81
|
+
${blockStructure.type === 'text' &&
|
|
82
|
+
html`
|
|
83
|
+
<form
|
|
84
|
+
data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
|
|
85
|
+
>
|
|
86
|
+
<label for="block-${data.id}">${blockStructure.label}</label>
|
|
87
|
+
<input
|
|
88
|
+
id="block-${data.id}"
|
|
89
|
+
name="value"
|
|
90
|
+
type="text"
|
|
91
|
+
value="${data.value}"
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
<input type="hidden" name="type" value="${blockStructure.type}" />
|
|
95
|
+
<input type="hidden" name="id" value="${data.id}" />
|
|
96
|
+
<input type="hidden" name="entryId" value="${entryId}" />
|
|
97
|
+
<input type="hidden" name="parentId" value="${parentId}" />
|
|
98
|
+
<input type="hidden" name="sort_order" value="${sortOrder}" />
|
|
99
|
+
<input
|
|
100
|
+
type="hidden"
|
|
101
|
+
name="path"
|
|
102
|
+
value="${fieldStructurePath.join('.')}"
|
|
103
|
+
/>
|
|
104
|
+
</form>
|
|
105
|
+
`}
|
|
106
|
+
${blockStructure.type === 'image' &&
|
|
107
|
+
html`
|
|
108
|
+
<form
|
|
109
|
+
data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
|
|
110
|
+
>
|
|
111
|
+
<label for="block-${data.id}">${blockStructure.label}</label>
|
|
112
|
+
<input
|
|
113
|
+
id="block-${data.id}"
|
|
114
|
+
name="value"
|
|
115
|
+
type="text"
|
|
116
|
+
value="${data.value}"
|
|
117
|
+
/>
|
|
118
|
+
|
|
119
|
+
<input type="hidden" name="type" value="${blockStructure.type}" />
|
|
120
|
+
<input type="hidden" name="id" value="${data.id}" />
|
|
121
|
+
<input type="hidden" name="entryId" value="${entryId}" />
|
|
122
|
+
<input type="hidden" name="parentId" value="${parentId}" />
|
|
123
|
+
<input type="hidden" name="sort_order" value="${sortOrder}" />
|
|
124
|
+
<input
|
|
125
|
+
type="hidden"
|
|
126
|
+
name="path"
|
|
127
|
+
value="${fieldStructurePath.join('.')}"
|
|
128
|
+
/>
|
|
129
|
+
</form>
|
|
130
|
+
`}
|
|
131
|
+
${blockStructure.type === 'markdown' &&
|
|
132
|
+
html`
|
|
133
|
+
<form
|
|
134
|
+
data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
|
|
135
|
+
>
|
|
136
|
+
<label for="block-${data.id}">${blockStructure.label}</label>
|
|
137
|
+
|
|
138
|
+
<textarea></textarea>
|
|
139
|
+
|
|
140
|
+
<!-- <input
|
|
141
|
+
id="block-${data.id}"
|
|
142
|
+
name="value"
|
|
143
|
+
type="text"
|
|
144
|
+
value="${data.value}"
|
|
145
|
+
/> -->
|
|
146
|
+
|
|
147
|
+
<input type="hidden" name="type" value="${blockStructure.type}" />
|
|
148
|
+
<input type="hidden" name="id" value="${data.id}" />
|
|
149
|
+
<input type="hidden" name="entryId" value="${entryId}" />
|
|
150
|
+
<input type="hidden" name="parentId" value="${parentId}" />
|
|
151
|
+
<input type="hidden" name="sort_order" value="${sortOrder}" />
|
|
152
|
+
<input
|
|
153
|
+
type="hidden"
|
|
154
|
+
name="path"
|
|
155
|
+
value="${fieldStructurePath.join('.')}"
|
|
156
|
+
/>
|
|
157
|
+
</form>
|
|
158
|
+
`}
|
|
159
|
+
${blockStructure.type === 'blocks' &&
|
|
160
|
+
BlockComponent(entryId, data.id, blockStructure, fieldStructurePath)}
|
|
161
|
+
`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default Field
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { html } from 'hono/html'
|
|
2
|
+
import { type Block } from '../types.ts'
|
|
3
|
+
import { type HtmlEscapedString } from 'hono/utils/html'
|
|
4
|
+
import Field from './Field.ts'
|
|
5
|
+
import {
|
|
6
|
+
buildStructurePath,
|
|
7
|
+
type StructurePath,
|
|
8
|
+
} from '../utils/build-structure-path.ts'
|
|
9
|
+
|
|
10
|
+
const Fields = (props: {
|
|
11
|
+
entryId: string | number
|
|
12
|
+
parentId: string | number
|
|
13
|
+
blockStructure: Block
|
|
14
|
+
structurePath: StructurePath
|
|
15
|
+
}): HtmlEscapedString | Promise<HtmlEscapedString> => {
|
|
16
|
+
const updatedPath = buildStructurePath(
|
|
17
|
+
props.blockStructure,
|
|
18
|
+
props.structurePath,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return html`
|
|
22
|
+
<div class="fields">
|
|
23
|
+
${props.blockStructure.fields
|
|
24
|
+
? props.blockStructure.fields?.map((field, idx) => {
|
|
25
|
+
return Field({
|
|
26
|
+
entryId: props.entryId,
|
|
27
|
+
parentId: props.parentId,
|
|
28
|
+
blockStructure: field,
|
|
29
|
+
sortOrder: idx,
|
|
30
|
+
structurePath: [...updatedPath, 'fields'],
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
: Field({
|
|
34
|
+
entryId: props.entryId,
|
|
35
|
+
parentId: props.parentId,
|
|
36
|
+
blockStructure: props.blockStructure,
|
|
37
|
+
structurePath: updatedPath,
|
|
38
|
+
})}
|
|
39
|
+
</div>
|
|
40
|
+
`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default Fields
|