@alstar/studio 0.0.0-beta.5 → 0.0.0-beta.6
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 +21 -29
- package/api/index.ts +9 -1
- package/api/mcp.ts +53 -0
- package/bin/alstar.ts +42 -0
- package/components/{AdminPanel/AdminPanel.ts → AdminPanel.ts} +22 -27
- package/components/Block.ts +51 -112
- package/components/Entries.ts +3 -3
- package/components/Entry.ts +9 -15
- package/components/Settings.ts +98 -0
- package/components/fields/Blocks.ts +118 -0
- package/components/fields/Text.ts +42 -0
- package/components/fields/index.ts +4 -0
- package/components/icons.ts +59 -0
- package/components/index.ts +1 -1
- package/components/layout.ts +2 -2
- package/index.ts +33 -14
- package/package.json +4 -3
- package/public/admin-panel.css +90 -0
- package/public/blocks.css +53 -0
- package/public/main.css +8 -0
- package/public/main.js +4 -0
- package/public/settings.css +24 -0
- package/queries/block-with-children.ts +74 -0
- package/queries/block.ts +29 -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 +84 -5
- package/utils/buildBlocksTree.ts +4 -4
- package/utils/define.ts +18 -4
- package/utils/get-or-create-row.ts +28 -0
- package/utils/startup-log.ts +9 -0
- package/components/AdminPanel/AdminPanel.css +0 -78
- package/components/Field.ts +0 -168
- package/components/Fields.ts +0 -43
- /package/{components/Entry.css → public/entry.css} +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
|
@@ -8,8 +8,12 @@ import { type Structure } from '../types.ts'
|
|
|
8
8
|
import { db } from '@alstar/db'
|
|
9
9
|
import Entries from '../components/Entries.ts'
|
|
10
10
|
import Entry from '../components/Entry.ts'
|
|
11
|
+
import {
|
|
12
|
+
blockWithChildren,
|
|
13
|
+
deleteBlockWithChildren,
|
|
14
|
+
} from '../queries/block-with-children.ts'
|
|
11
15
|
|
|
12
|
-
export
|
|
16
|
+
export default (structure: Structure) => {
|
|
13
17
|
const app = new Hono<{ Bindings: HttpBindings }>()
|
|
14
18
|
|
|
15
19
|
app.post('/block', async (c) => {
|
|
@@ -17,14 +21,14 @@ export const sectionRoutes = (structure: Structure) => {
|
|
|
17
21
|
const formData = await c.req.formData()
|
|
18
22
|
const data = Object.fromEntries(formData.entries())
|
|
19
23
|
|
|
20
|
-
const row = structure.
|
|
24
|
+
const row = structure[data.name?.toString()]
|
|
21
25
|
|
|
22
26
|
if (!row) return
|
|
23
27
|
|
|
24
28
|
db.insertInto('blocks', {
|
|
25
|
-
name:
|
|
26
|
-
label: row.label
|
|
27
|
-
type: row.type
|
|
29
|
+
name: data.name?.toString(),
|
|
30
|
+
label: row.label,
|
|
31
|
+
type: row.type,
|
|
28
32
|
})
|
|
29
33
|
|
|
30
34
|
await stream.writeSSE({
|
|
@@ -37,26 +41,19 @@ export const sectionRoutes = (structure: Structure) => {
|
|
|
37
41
|
app.post('/new-block', async (c) => {
|
|
38
42
|
return streamSSE(c, async (stream) => {
|
|
39
43
|
const formData = await c.req.formData()
|
|
40
|
-
const
|
|
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
|
|
44
|
+
const data = Object.fromEntries(formData)
|
|
48
45
|
|
|
49
46
|
db.insertInto('blocks', {
|
|
50
|
-
type: data.type,
|
|
51
|
-
name: data.name,
|
|
52
|
-
label: data.label,
|
|
53
|
-
|
|
54
|
-
sort_order,
|
|
47
|
+
type: data.type.toString(),
|
|
48
|
+
name: data.name.toString(),
|
|
49
|
+
label: data.label.toString(),
|
|
50
|
+
parent_id: data.parent_id.toString(),
|
|
51
|
+
sort_order: data.sort_order.toString(),
|
|
55
52
|
})
|
|
56
53
|
|
|
57
54
|
await stream.writeSSE({
|
|
58
55
|
event: 'datastar-patch-elements',
|
|
59
|
-
data: `elements ${stripNewlines(Entry({ entryId: data.entry_id
|
|
56
|
+
data: `elements ${stripNewlines(Entry({ entryId: parseInt(data.entry_id.toString()) }))}`,
|
|
60
57
|
})
|
|
61
58
|
})
|
|
62
59
|
})
|
|
@@ -99,22 +96,17 @@ export const sectionRoutes = (structure: Structure) => {
|
|
|
99
96
|
const formData = await c.req.formData()
|
|
100
97
|
|
|
101
98
|
const id = formData.get('id')?.toString()
|
|
99
|
+
const entryId = formData.get('entry_id')?.toString()
|
|
102
100
|
|
|
103
|
-
if (!id) return
|
|
101
|
+
if (!id || !entryId) return
|
|
104
102
|
|
|
105
|
-
const transaction = db.database.prepare(
|
|
106
|
-
update blocks
|
|
107
|
-
set
|
|
108
|
-
status = 'disabled'
|
|
109
|
-
where
|
|
110
|
-
id = ?
|
|
111
|
-
`)
|
|
103
|
+
const transaction = db.database.prepare(deleteBlockWithChildren)
|
|
112
104
|
|
|
113
|
-
transaction.
|
|
105
|
+
transaction.all(id)
|
|
114
106
|
|
|
115
107
|
await stream.writeSSE({
|
|
116
108
|
event: 'datastar-patch-elements',
|
|
117
|
-
data: `elements ${stripNewlines(
|
|
109
|
+
data: `elements ${stripNewlines(Entry({ entryId: parseInt(entryId.toString()) }))}`,
|
|
118
110
|
})
|
|
119
111
|
})
|
|
120
112
|
})
|
package/api/index.ts
CHANGED
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
|
+
}
|
package/bin/alstar.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
|
|
6
|
+
// process.removeAllListeners('warning')
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
|
|
10
|
+
// Dev-only auto watch (opt out with ALSTAR_NO_WATCH=true)
|
|
11
|
+
const isDev =
|
|
12
|
+
process.env.NODE_ENV !== 'production' &&
|
|
13
|
+
process.env.ALSTAR_NO_WATCH !== 'true'
|
|
14
|
+
const alreadyBootstrapped = process.env.ALSTAR_WATCH_BOOTSTRAPPED === '1'
|
|
15
|
+
|
|
16
|
+
if (isDev && !alreadyBootstrapped) {
|
|
17
|
+
// Start a watcher ON THIS FILE and mark the environment so the child won't re-spawn
|
|
18
|
+
const childProcess = spawn(
|
|
19
|
+
process.execPath,
|
|
20
|
+
['--watch', __filename, ...process.argv.slice(2)],
|
|
21
|
+
// [__filename, ...process.argv.slice(2)],
|
|
22
|
+
{
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
env: { ...process.env, ALSTAR_WATCH_BOOTSTRAPPED: '1' },
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
// Important: exit this launcher so only the watched child keeps running
|
|
28
|
+
process.on('SIGINT', () => {
|
|
29
|
+
childProcess.kill('SIGINT')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
process.on('SIGTERM', () => {
|
|
33
|
+
childProcess.kill('SIGTERM')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
process.exit(0)
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --- Your actual CLI logic below ---
|
|
41
|
+
// console.log(import.meta.dirname, path.resolve('.'))
|
|
42
|
+
// import("./main.js").then(m => m.default());
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { html } from 'hono/html'
|
|
2
|
-
import type { Structure } from '
|
|
3
|
-
import { logo } from '
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import * as icons from '../icons.ts'
|
|
2
|
+
import type { Structure } from '../types.ts'
|
|
3
|
+
import { logo } from './icons.ts'
|
|
4
|
+
import Entries from './Entries.ts'
|
|
5
|
+
import * as icons from './icons.ts'
|
|
7
6
|
|
|
8
7
|
export default (structure: Structure) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
rel="stylesheet"
|
|
12
|
-
href="${rootdir}/components/AdminPanel/AdminPanel.css"
|
|
13
|
-
/>
|
|
8
|
+
const entries = Object.entries(structure)
|
|
9
|
+
const type = typeof entries[0][1] !== 'string' ? entries[0][1].type : null
|
|
14
10
|
|
|
11
|
+
return html`
|
|
15
12
|
<div class="admin-panel">
|
|
16
13
|
<h1>
|
|
17
14
|
<a href="/admin" aria-label="Go to dashboard"> ${logo} </a>
|
|
@@ -21,24 +18,9 @@ export default (structure: Structure) => {
|
|
|
21
18
|
<form
|
|
22
19
|
data-on-submit="@post('/admin/api/block', { contentType: 'form' })"
|
|
23
20
|
>
|
|
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
21
|
<!-- TODO: currently only handles a single entry type -->
|
|
36
|
-
${
|
|
37
|
-
? html`<input
|
|
38
|
-
type="hidden"
|
|
39
|
-
name="type"
|
|
40
|
-
value="${structure[0].type}"
|
|
41
|
-
/>
|
|
22
|
+
${entries.length
|
|
23
|
+
? html`<input type="hidden" name="name" value="${type}" />
|
|
42
24
|
<button
|
|
43
25
|
class="ghost"
|
|
44
26
|
style="padding: 10px; margin: 0 -13px; display: flex;"
|
|
@@ -52,6 +34,19 @@ export default (structure: Structure) => {
|
|
|
52
34
|
</aside>
|
|
53
35
|
|
|
54
36
|
${Entries()}
|
|
37
|
+
|
|
38
|
+
<footer>
|
|
39
|
+
<a
|
|
40
|
+
role="button"
|
|
41
|
+
href="/admin/settings"
|
|
42
|
+
class="ghost"
|
|
43
|
+
style="padding: 10px; margin: 0 -13px; display: flex;"
|
|
44
|
+
data-tooltip="Settings"
|
|
45
|
+
data-placement="right"
|
|
46
|
+
>
|
|
47
|
+
${icons.cog}
|
|
48
|
+
</a>
|
|
49
|
+
</footer>
|
|
55
50
|
</div>
|
|
56
51
|
`
|
|
57
52
|
}
|
package/components/Block.ts
CHANGED
|
@@ -1,116 +1,55 @@
|
|
|
1
1
|
import { html } from 'hono/html'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
entryId
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
2
|
+
import type { Block, BlockDef, FieldDef } from '../types.ts'
|
|
3
|
+
import { type HtmlEscapedString } from 'hono/utils/html'
|
|
4
|
+
import { Field } from './fields/index.ts'
|
|
5
|
+
|
|
6
|
+
export default (props: {
|
|
7
|
+
entryId: number
|
|
8
|
+
parentId: number
|
|
9
|
+
blockStructure: BlockDef | Block | FieldDef
|
|
10
|
+
name: string
|
|
11
|
+
sortOrder?: number
|
|
12
|
+
}): HtmlEscapedString | Promise<HtmlEscapedString> => {
|
|
13
|
+
const { entryId, parentId, blockStructure, name, sortOrder = 0 } = props
|
|
14
|
+
|
|
15
|
+
if (!blockStructure) return html`<p>No block</p>`
|
|
16
|
+
|
|
17
|
+
let entries: [string, BlockDef | Block | FieldDef][] = []
|
|
18
|
+
|
|
19
|
+
const fieldTypes = ['text', 'image', 'markdown', 'slug']
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if (fieldTypes.includes(blockStructure.type)) {
|
|
23
|
+
entries = [[name, blockStructure]]
|
|
24
|
+
} else if (blockStructure.type === 'blocks') {
|
|
25
|
+
entries = Object.entries(blockStructure.children)
|
|
26
|
+
} else if (blockStructure.fields) {
|
|
27
|
+
entries = Object.entries(blockStructure.fields)
|
|
28
|
+
} else {
|
|
29
|
+
console.log(blockStructure)
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.log(error)
|
|
33
|
+
}
|
|
24
34
|
|
|
25
35
|
return html`
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
`
|
|
36
|
+
${entries.map(([name, field]) => {
|
|
37
|
+
switch (field.type) {
|
|
38
|
+
case 'text': {
|
|
39
|
+
return Field.Text({ entryId, parentId, name, field, sortOrder })
|
|
40
|
+
}
|
|
41
|
+
case 'slug': {
|
|
42
|
+
return Field.Text({ entryId, parentId, name, field, sortOrder })
|
|
43
|
+
}
|
|
44
|
+
case 'markdown': {
|
|
45
|
+
return Field.Text({ entryId, parentId, name, field, sortOrder })
|
|
46
|
+
}
|
|
47
|
+
case 'image': {
|
|
48
|
+
return Field.Text({ entryId, parentId, name, field, sortOrder })
|
|
49
|
+
}
|
|
50
|
+
case 'blocks': {
|
|
51
|
+
return Field.Blocks({ entryId, parentId, name, field, sortOrder })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
})}`
|
|
116
55
|
}
|
package/components/Entries.ts
CHANGED
|
@@ -3,14 +3,14 @@ import { query } from '../index.ts'
|
|
|
3
3
|
import * as icons from './icons.ts'
|
|
4
4
|
|
|
5
5
|
export default () => {
|
|
6
|
-
const entries = query.blocks({
|
|
6
|
+
const entries = query.blocks({ parent_id: null, status: 'enabled' })
|
|
7
7
|
|
|
8
8
|
return html`
|
|
9
9
|
<section id="entries">
|
|
10
10
|
<ul>
|
|
11
11
|
${entries?.map((block) => {
|
|
12
12
|
const title = query.block({
|
|
13
|
-
|
|
13
|
+
parent_id: block.id.toString(),
|
|
14
14
|
name: 'title',
|
|
15
15
|
})
|
|
16
16
|
|
|
@@ -27,7 +27,7 @@ export default () => {
|
|
|
27
27
|
<button
|
|
28
28
|
data-tooltip="Remove"
|
|
29
29
|
data-placement="right"
|
|
30
|
-
class="ghost"
|
|
30
|
+
class="ghost text-secondary"
|
|
31
31
|
style="padding: 0"
|
|
32
32
|
type="submit"
|
|
33
33
|
>
|
package/components/Entry.ts
CHANGED
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
import { html } from 'hono/html'
|
|
2
2
|
import { query } from '../queries/index.ts'
|
|
3
|
-
import { rootdir } from '../index.ts'
|
|
4
|
-
import
|
|
5
|
-
import Fields from './Fields.ts'
|
|
6
|
-
import { buildStructurePath } from '../utils/build-structure-path.ts'
|
|
3
|
+
import { rootdir, structure } from '../index.ts'
|
|
4
|
+
import Block from './Block.ts'
|
|
7
5
|
|
|
8
|
-
export default (props: { entryId: number
|
|
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 =
|
|
14
|
-
(block) => block.name === data.name,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
const structurePath = buildStructurePath(blockStructure)
|
|
11
|
+
const blockStructure = structure[data.name]
|
|
18
12
|
|
|
19
13
|
return html`
|
|
20
14
|
<div id="entry">
|
|
21
|
-
<link rel="stylesheet" href="${rootdir}/components/Entry.css" />
|
|
22
|
-
|
|
23
15
|
<div class="entry">
|
|
24
16
|
${blockStructure &&
|
|
25
|
-
|
|
17
|
+
Block({
|
|
26
18
|
entryId: props.entryId,
|
|
27
19
|
parentId: props.entryId,
|
|
28
|
-
blockStructure,
|
|
29
|
-
|
|
20
|
+
blockStructure: blockStructure,
|
|
21
|
+
name: data.name,
|
|
30
22
|
})}
|
|
31
23
|
</div>
|
|
24
|
+
|
|
25
|
+
<!-- <pre><code>{JSON.stringify(blockStructure, null, 2)}</code></pre> -->
|
|
32
26
|
</div>
|
|
33
27
|
`
|
|
34
28
|
}
|