@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.
- package/api/api-key.ts +74 -0
- package/api/block.ts +82 -42
- package/api/index.ts +9 -1
- package/api/mcp.ts +53 -0
- package/components/AdminPanel.ts +74 -0
- package/components/BlockFieldRenderer.ts +121 -0
- package/components/BlockRenderer.ts +22 -0
- package/components/Entries.ts +5 -4
- package/components/Entry.ts +13 -21
- package/components/FieldRenderer.ts +35 -0
- package/components/Render.ts +46 -0
- package/components/Settings.ts +98 -0
- package/components/{layout.ts → SiteLayout.ts} +9 -13
- package/components/fields/Markdown.ts +44 -0
- package/components/fields/Text.ts +42 -0
- package/components/fields/index.ts +4 -0
- package/components/icons.ts +59 -0
- package/index.ts +52 -30
- package/package.json +3 -2
- package/pages/entry/[id].ts +17 -0
- package/{components → pages}/index.ts +7 -4
- package/pages/settings.ts +10 -0
- package/public/studio/admin-panel.css +103 -0
- package/public/studio/blocks.css +53 -0
- package/public/studio/main.css +162 -0
- package/public/studio/main.js +10 -0
- package/public/studio/markdown-editor.js +34 -0
- package/public/studio/settings.css +24 -0
- package/public/studio/sortable-list.js +40 -0
- package/queries/block-with-children.ts +74 -0
- package/queries/block.ts +31 -40
- package/queries/db-types.ts +15 -0
- package/queries/getBlockTrees-2.ts +0 -0
- package/queries/getBlockTrees.ts +316 -0
- package/queries/getBlocks.ts +214 -0
- package/queries/index.ts +1 -0
- package/queries/structure-types.ts +97 -0
- package/schemas.ts +14 -3
- package/types.ts +123 -6
- package/utils/buildBlocksTree.ts +4 -4
- package/utils/define.ts +31 -5
- package/utils/file-based-router.ts +1 -0
- package/utils/get-or-create-row.ts +41 -0
- package/utils/startup-log.ts +9 -0
- package/components/AdminPanel/AdminPanel.css +0 -78
- package/components/AdminPanel/AdminPanel.ts +0 -57
- package/components/Block.ts +0 -116
- package/components/Field.ts +0 -168
- package/components/Fields.ts +0 -43
- package/public/main.css +0 -95
- package/public/main.js +0 -44
- /package/{components/Entry.css → public/studio/entry.css} +0 -0
- /package/public/{favicon.svg → studio/favicon.svg} +0 -0
package/types.ts
CHANGED
|
@@ -2,15 +2,130 @@ import { type HttpBindings } from '@hono/node-server'
|
|
|
2
2
|
import { type Context } from 'hono'
|
|
3
3
|
import { type HonoOptions } from 'hono/hono-base'
|
|
4
4
|
import { type BlankInput, type BlankEnv } from 'hono/types'
|
|
5
|
+
import {
|
|
6
|
+
BlockFieldInstance,
|
|
7
|
+
BlockInstance,
|
|
8
|
+
FieldInstance,
|
|
9
|
+
} from './utils/define.ts'
|
|
10
|
+
|
|
11
|
+
export type PrimitiveField = {
|
|
12
|
+
name: string
|
|
13
|
+
label: string
|
|
14
|
+
type: 'text' | 'slug' | 'markdown' | 'image'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type BlockField = {
|
|
18
|
+
name: string
|
|
19
|
+
label: string
|
|
20
|
+
type: 'blocks'
|
|
21
|
+
children: Record<string, Field | Block>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type Field = PrimitiveField | BlockField
|
|
5
25
|
|
|
6
26
|
export type Block = {
|
|
7
27
|
name: string
|
|
8
28
|
label: string
|
|
9
29
|
type: string
|
|
10
|
-
fields
|
|
30
|
+
fields: Record<string, Field | Block>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type Structure = Record<string, BlockDefStructure>
|
|
34
|
+
|
|
35
|
+
// --- Field & block definitions ---
|
|
36
|
+
type FieldType = 'text' | 'slug' | 'markdown' | 'image'
|
|
37
|
+
|
|
38
|
+
interface BaseField {
|
|
39
|
+
label: string
|
|
40
|
+
type: FieldType
|
|
41
|
+
description?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface TextField extends BaseField {
|
|
45
|
+
type: 'text' | 'slug' | 'markdown'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface TextFieldStructure extends TextField {
|
|
49
|
+
instanceOf: typeof FieldInstance
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ImageField extends BaseField {
|
|
53
|
+
type: 'image'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface ImageFieldStructure extends ImageField {
|
|
57
|
+
instanceOf: typeof FieldInstance
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface BlocksFieldDef {
|
|
61
|
+
label: string
|
|
62
|
+
type: 'blocks'
|
|
63
|
+
description?: string
|
|
64
|
+
children: Record<string, BlockDefStructure | FieldDefStructure>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface BlocksFieldDefStructure extends BlocksFieldDef {
|
|
68
|
+
instanceOf: typeof BlockFieldInstance
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type FieldDef = TextField | ImageField
|
|
72
|
+
export type FieldDefStructure = TextFieldStructure | ImageFieldStructure
|
|
73
|
+
|
|
74
|
+
export interface BlockDef {
|
|
75
|
+
label: string
|
|
76
|
+
type: string
|
|
77
|
+
fields: Record<string, FieldDefStructure | BlocksFieldDefStructure>
|
|
78
|
+
description?: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface BlockDefStructure extends BlockDef {
|
|
82
|
+
instanceOf: typeof BlockInstance
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// type DBDefaults = {
|
|
86
|
+
// id: number
|
|
87
|
+
// created_at: string
|
|
88
|
+
// updated_at: string
|
|
89
|
+
// name: string
|
|
90
|
+
// label: string
|
|
91
|
+
// // type: string
|
|
92
|
+
// sort_order: number
|
|
93
|
+
// value: string
|
|
94
|
+
// options: string | null
|
|
95
|
+
// status: 'enabled' | 'disabled'
|
|
96
|
+
// parent_id: number | null
|
|
97
|
+
// depth: number
|
|
98
|
+
// }
|
|
99
|
+
|
|
100
|
+
type BaseDBResult = {
|
|
101
|
+
id: number
|
|
102
|
+
created_at: string
|
|
103
|
+
updated_at: string
|
|
104
|
+
name: string
|
|
105
|
+
label: string
|
|
106
|
+
sort_order: number
|
|
107
|
+
value: string | null
|
|
108
|
+
options: any
|
|
109
|
+
status: 'enabled' | 'disabled'
|
|
110
|
+
parent_id: number | null
|
|
111
|
+
depth: number
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type DBPrimitiveFieldResult = BaseDBResult & {
|
|
115
|
+
type: FieldDef
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type DBBlockFieldResult = BaseDBResult & {
|
|
119
|
+
type: 'blocks'
|
|
120
|
+
children: DBBlockResult[]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type DBBlockResult = BaseDBResult & {
|
|
124
|
+
type: string
|
|
125
|
+
fields: Record<string, DBFieldResult>
|
|
11
126
|
}
|
|
12
127
|
|
|
13
|
-
export type
|
|
128
|
+
export type DBFieldResult = DBPrimitiveFieldResult & DBBlockFieldResult
|
|
14
129
|
|
|
15
130
|
export type DBBlock = Block & {
|
|
16
131
|
id: number
|
|
@@ -18,17 +133,19 @@ export type DBBlock = Block & {
|
|
|
18
133
|
updated_at: string
|
|
19
134
|
value: string | null
|
|
20
135
|
sort_order: number | null
|
|
21
|
-
|
|
136
|
+
parent_id: number | null
|
|
22
137
|
options: number | null
|
|
23
138
|
}
|
|
24
139
|
|
|
140
|
+
export type BlockStatus = 'enabled' | 'disabled'
|
|
141
|
+
|
|
25
142
|
export type StudioConfig = {
|
|
26
|
-
siteName
|
|
143
|
+
siteName?: string
|
|
27
144
|
honoConfig?: HonoOptions<BlankEnv>
|
|
145
|
+
port?: number
|
|
146
|
+
structure: Structure
|
|
28
147
|
}
|
|
29
148
|
|
|
30
|
-
export type BlockStatus = 'enabled' | 'disabled'
|
|
31
|
-
|
|
32
149
|
export type RequestContext = Context<
|
|
33
150
|
{ Bindings: HttpBindings },
|
|
34
151
|
string,
|
package/utils/buildBlocksTree.ts
CHANGED
|
@@ -6,7 +6,7 @@ type Block = {
|
|
|
6
6
|
sort_order: number
|
|
7
7
|
value: string | null
|
|
8
8
|
options: any // JSON-parsed if necessary
|
|
9
|
-
|
|
9
|
+
parent_id: number | null
|
|
10
10
|
depth: number
|
|
11
11
|
// ... you can add other fields if needed
|
|
12
12
|
}
|
|
@@ -26,13 +26,13 @@ export function buildBlockTree(blocks: Block[]): BlockWithChildren {
|
|
|
26
26
|
for (const block of blocks) {
|
|
27
27
|
const current = blockMap.get(block.id)!
|
|
28
28
|
|
|
29
|
-
if (block.
|
|
30
|
-
const parent = blockMap.get(block.
|
|
29
|
+
if (block.parent_id != null) {
|
|
30
|
+
const parent = blockMap.get(block.parent_id)
|
|
31
31
|
if (parent) {
|
|
32
32
|
parent.fields.push(current)
|
|
33
33
|
} else {
|
|
34
34
|
console.warn(
|
|
35
|
-
`Parent with id ${block.
|
|
35
|
+
`Parent with id ${block.parent_id} not found for block ${block.id}`,
|
|
36
36
|
)
|
|
37
37
|
}
|
|
38
38
|
} else {
|
package/utils/define.ts
CHANGED
|
@@ -1,15 +1,41 @@
|
|
|
1
|
-
import { type Context } from 'hono'
|
|
2
1
|
import * as types from '../types.ts'
|
|
3
|
-
import { type HttpBindings } from '@hono/node-server'
|
|
4
|
-
import { type BlankInput } from 'hono/types'
|
|
5
2
|
import { type HtmlEscapedString } from './html.ts'
|
|
6
3
|
|
|
7
4
|
export const defineConfig = (config: types.StudioConfig) => config
|
|
8
5
|
|
|
9
|
-
export const defineStructure = (structure: types.Structure) => structure
|
|
10
|
-
|
|
11
6
|
export const defineEntry = (
|
|
12
7
|
fn: (
|
|
13
8
|
c: types.RequestContext,
|
|
14
9
|
) => HtmlEscapedString | Promise<HtmlEscapedString>,
|
|
15
10
|
) => fn
|
|
11
|
+
|
|
12
|
+
export const FieldInstance = Symbol('field')
|
|
13
|
+
export const BlockFieldInstance = Symbol('blockfield')
|
|
14
|
+
export const BlockInstance = Symbol('block')
|
|
15
|
+
|
|
16
|
+
// --- Identity helpers (preserve literal types) ---
|
|
17
|
+
export function defineField(field: types.FieldDef): types.FieldDefStructure {
|
|
18
|
+
return {
|
|
19
|
+
...field,
|
|
20
|
+
instanceOf: FieldInstance,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function defineBlockField(
|
|
25
|
+
field: types.BlocksFieldDef,
|
|
26
|
+
): types.BlocksFieldDefStructure {
|
|
27
|
+
return {
|
|
28
|
+
...field,
|
|
29
|
+
instanceOf: BlockFieldInstance,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function defineBlock(block: types.BlockDef): types.BlockDefStructure {
|
|
34
|
+
return { ...block, instanceOf: BlockInstance }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function defineStructure(
|
|
38
|
+
structure: Record<string, types.BlockDefStructure>,
|
|
39
|
+
) {
|
|
40
|
+
return structure
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { db } from '@alstar/db'
|
|
2
|
+
import { query } from '../queries/index.ts'
|
|
3
|
+
import type {
|
|
4
|
+
BlockDefStructure,
|
|
5
|
+
BlocksFieldDefStructure,
|
|
6
|
+
FieldDefStructure,
|
|
7
|
+
} from '../types.ts'
|
|
8
|
+
|
|
9
|
+
export function getOrCreateRow(props: {
|
|
10
|
+
parentId: string | number | null
|
|
11
|
+
name: string
|
|
12
|
+
field: BlockDefStructure | BlocksFieldDefStructure | FieldDefStructure
|
|
13
|
+
sortOrder?: number
|
|
14
|
+
id?: number
|
|
15
|
+
}) {
|
|
16
|
+
const { parentId, name, field, sortOrder = 0, id } = props
|
|
17
|
+
|
|
18
|
+
if (id) {
|
|
19
|
+
return query.block({
|
|
20
|
+
id: id?.toString(),
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const data = query.block({
|
|
25
|
+
parent_id: parentId?.toString() || null,
|
|
26
|
+
name: name,
|
|
27
|
+
sort_order: sortOrder.toString(),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if (data) return data
|
|
31
|
+
|
|
32
|
+
const change = db.insertInto('blocks', {
|
|
33
|
+
name: name?.toString(),
|
|
34
|
+
label: field.label?.toString(),
|
|
35
|
+
type: field.type?.toString(),
|
|
36
|
+
sort_order: sortOrder,
|
|
37
|
+
parent_id: parentId,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return query.block({ id: change.lastInsertRowid.toString() })
|
|
41
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import packageJSON from '../package.json' with { type: 'json' }
|
|
2
|
+
|
|
3
|
+
export default ({ port }: { port: number }) => {
|
|
4
|
+
console.log('\x1b[32m%s\x1b[0m', '╭───────────────────────╮')
|
|
5
|
+
console.log('\x1b[32m%s\x1b[0m', '│ Alstar Studio │')
|
|
6
|
+
console.log('\x1b[32m%s\x1b[0m', `│ ${packageJSON.version}${' '.repeat(22 - packageJSON.version.length)}│`)
|
|
7
|
+
console.log('\x1b[32m%s\x1b[0m', `│ http://localhost:${port} │`)
|
|
8
|
+
console.log('\x1b[32m%s\x1b[0m', '╰───────────────────────╯')
|
|
9
|
+
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
.admin-panel {
|
|
2
|
-
/* background: hsla(0, 0%, 0%, 0.1); */
|
|
3
|
-
padding: 40px;
|
|
4
|
-
|
|
5
|
-
height: 100%;
|
|
6
|
-
min-height: inherit;
|
|
7
|
-
|
|
8
|
-
min-width: 250px;
|
|
9
|
-
|
|
10
|
-
> h1 {
|
|
11
|
-
padding-bottom: 1rem;
|
|
12
|
-
|
|
13
|
-
a {
|
|
14
|
-
display: flex;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
svg {
|
|
18
|
-
height: 1.6rem;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
form {
|
|
23
|
-
padding-bottom: 1rem;
|
|
24
|
-
|
|
25
|
-
button {
|
|
26
|
-
margin: 10px 0px 20px;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
#entries ul {
|
|
31
|
-
padding: 0;
|
|
32
|
-
margin-inline: -1rem;
|
|
33
|
-
|
|
34
|
-
form {
|
|
35
|
-
display: flex;
|
|
36
|
-
padding-bottom: 0;
|
|
37
|
-
|
|
38
|
-
button {
|
|
39
|
-
margin: 0;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
> li {
|
|
44
|
-
margin-bottom: 0px;
|
|
45
|
-
border-radius: 8px;
|
|
46
|
-
display: flex;
|
|
47
|
-
justify-content: space-between;
|
|
48
|
-
align-items: stretch;
|
|
49
|
-
list-style: none;
|
|
50
|
-
|
|
51
|
-
a {
|
|
52
|
-
text-decoration: none;
|
|
53
|
-
width: 100%;
|
|
54
|
-
padding: 0.5rem 1rem;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
button {
|
|
58
|
-
border-radius: 7px;
|
|
59
|
-
opacity: 0;
|
|
60
|
-
transition: opacity 100px;
|
|
61
|
-
|
|
62
|
-
svg {
|
|
63
|
-
margin: 0.5rem 1rem;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
#entries ul {
|
|
71
|
-
> li:hover {
|
|
72
|
-
background-color: var(--pico-form-element-background-color);
|
|
73
|
-
|
|
74
|
-
button {
|
|
75
|
-
opacity: 1;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
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
|
-
}
|
package/components/Block.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
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_block_id: 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
|
-
}
|