@alstar/studio 0.0.0-beta.16 → 0.0.0-beta.18
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 +0 -14
- package/components/AdminPanel.ts +11 -5
- package/components/BlockFieldRenderer.ts +25 -19
- package/components/BlockRenderer.ts +4 -4
- package/components/Entries.ts +1 -1
- package/components/Entry.ts +13 -7
- package/components/FieldRenderer.ts +8 -7
- package/components/LivePreview.ts +37 -0
- package/components/Render.ts +2 -2
- package/components/SiteLayout.ts +1 -4
- package/components/fields/Markdown.ts +10 -3
- package/components/fields/Reference.ts +11 -7
- package/components/fields/Slug.ts +6 -6
- package/components/fields/Text.ts +13 -8
- package/components/icons.ts +3 -0
- package/components/settings/ApiKeys.ts +4 -4
- package/components/settings/Backup.ts +3 -3
- package/components/settings/Users.ts +1 -1
- package/index.ts +11 -10
- package/package.json +4 -5
- package/pages/entry/[id].ts +7 -1
- package/pages/error.ts +7 -6
- package/pages/login.ts +1 -1
- package/pages/register.ts +2 -2
- package/public/studio/css/admin-panel.css +27 -9
- package/public/studio/css/blocks-field.css +25 -0
- package/public/studio/css/entry-page.css +4 -0
- package/public/studio/css/entry.css +35 -0
- package/public/studio/css/field.css +14 -0
- package/public/studio/css/live-preview.css +25 -0
- package/public/studio/css/settings.css +4 -0
- package/public/studio/js/live-preview.js +26 -0
- package/public/studio/js/markdown-editor.js +6 -0
- package/public/studio/js/sortable-list.js +6 -4
- package/public/studio/main.css +11 -13
- package/public/studio/main.js +1 -0
- package/queries/block.ts +127 -105
- package/queries/index.ts +3 -2
- package/types.ts +39 -69
- package/utils/define.ts +3 -1
- package/utils/refresher.ts +56 -0
- package/utils/renderSSE.ts +8 -3
- package/utils/startup-log.ts +4 -4
- package/queries/block-2.ts +0 -339
- package/queries/db-types.ts +0 -15
- package/queries/getBlockTrees-2.ts +0 -71
- package/queries/getBlocks.ts +0 -214
- package/queries/structure-types.ts +0 -97
- package/utils/buildBlocksTree.ts +0 -44
package/api/block.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { streamSSE } from 'hono/streaming'
|
|
|
5
5
|
import { sql } from '../utils/sql.ts'
|
|
6
6
|
import { db } from '@alstar/db'
|
|
7
7
|
import {
|
|
8
|
-
blockWithChildren,
|
|
9
8
|
deleteBlockWithChildren,
|
|
10
9
|
} from '../queries/block-with-children.ts'
|
|
11
10
|
import { query } from '../queries/index.ts'
|
|
@@ -57,19 +56,6 @@ app.patch('/block', async (c) => {
|
|
|
57
56
|
transaction.run(value, id)
|
|
58
57
|
|
|
59
58
|
await renderSSE(stream, c)
|
|
60
|
-
|
|
61
|
-
// if (entryId === parentId && name?.toString() === 'title') {
|
|
62
|
-
// const rootBlock = query.block({
|
|
63
|
-
// id: parentId?.toString() || null,
|
|
64
|
-
// })
|
|
65
|
-
|
|
66
|
-
// if (rootBlock.type !== 'single') {
|
|
67
|
-
// await stream.writeSSE({
|
|
68
|
-
// event: 'datastar-patch-elements',
|
|
69
|
-
// data: `elements <a href="/studio/entry/${entryId}" id="block_link_${entryId}">${value}</a>`,
|
|
70
|
-
// })
|
|
71
|
-
// }
|
|
72
|
-
// }
|
|
73
59
|
})
|
|
74
60
|
})
|
|
75
61
|
|
package/components/AdminPanel.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { html } from 'hono/html'
|
|
|
2
2
|
import { logo } from './icons.ts'
|
|
3
3
|
import Entries from './Entries.ts'
|
|
4
4
|
import * as icons from './icons.ts'
|
|
5
|
-
import { studioStructure } from '../index.ts'
|
|
5
|
+
import { studioConfig, studioStructure } from '../index.ts'
|
|
6
6
|
import { getOrCreateRow } from '../utils/get-or-create-row.ts'
|
|
7
7
|
|
|
8
8
|
export default () => {
|
|
@@ -10,9 +10,15 @@ export default () => {
|
|
|
10
10
|
|
|
11
11
|
return html`
|
|
12
12
|
<div class="admin-panel" id="admin_panel">
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
</
|
|
13
|
+
<!-- <button class="ghost toggle-button">
|
|
14
|
+
{icons.bars}
|
|
15
|
+
</button> -->
|
|
16
|
+
|
|
17
|
+
<header>
|
|
18
|
+
<h1 class="title">
|
|
19
|
+
<a href="/studio" aria-label="Go to dashboard"> ${studioConfig.admin?.logo ?? logo} </a>
|
|
20
|
+
</h1>
|
|
21
|
+
</header>
|
|
16
22
|
|
|
17
23
|
<aside style="width: 100%;">
|
|
18
24
|
${entries.map(([name, block]) => {
|
|
@@ -37,7 +43,7 @@ export default () => {
|
|
|
37
43
|
|
|
38
44
|
return html`
|
|
39
45
|
<form
|
|
40
|
-
data-on
|
|
46
|
+
data-on:submit="@post('/studio/api/block', {
|
|
41
47
|
contentType: 'form',
|
|
42
48
|
headers: {
|
|
43
49
|
'render': 'AdminPanel',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { query } from '../queries/index.ts'
|
|
2
|
-
import type { BlocksFieldDefStructure } from '../types.ts'
|
|
2
|
+
import type { BlocksFieldDef, BlocksFieldDefStructure } from '../types.ts'
|
|
3
3
|
import { BlockInstance } from '../utils/define.ts'
|
|
4
4
|
import { getOrCreateRow } from '../utils/get-or-create-row.ts'
|
|
5
5
|
import { html } from '../utils/html.ts'
|
|
@@ -7,8 +7,8 @@ import * as icons from './icons.ts'
|
|
|
7
7
|
import Render from './Render.ts'
|
|
8
8
|
|
|
9
9
|
export default (props: {
|
|
10
|
-
entryId: number
|
|
11
|
-
parentId: number
|
|
10
|
+
entryId: number | string
|
|
11
|
+
parentId: number | string
|
|
12
12
|
name: string
|
|
13
13
|
structure: BlocksFieldDefStructure
|
|
14
14
|
id?: number
|
|
@@ -32,7 +32,7 @@ export default (props: {
|
|
|
32
32
|
|
|
33
33
|
return html`
|
|
34
34
|
<div class="blocks-field">
|
|
35
|
-
<header>
|
|
35
|
+
<header data-on:click="console.log('hehehe')">
|
|
36
36
|
<p>${structure.label}</p>
|
|
37
37
|
|
|
38
38
|
<details class="dropdown">
|
|
@@ -42,10 +42,10 @@ export default (props: {
|
|
|
42
42
|
return html`
|
|
43
43
|
<li>
|
|
44
44
|
<form
|
|
45
|
-
data-on
|
|
45
|
+
data-on:submit="@post('/studio/api/block', {
|
|
46
46
|
contentType: 'form',
|
|
47
47
|
headers: {
|
|
48
|
-
render: 'Entry',
|
|
48
|
+
render: 'Entry LivePreview',
|
|
49
49
|
props: '${JSON.stringify({ entryId: entryId })}'
|
|
50
50
|
}
|
|
51
51
|
})"
|
|
@@ -69,8 +69,6 @@ export default (props: {
|
|
|
69
69
|
</details>
|
|
70
70
|
</header>
|
|
71
71
|
|
|
72
|
-
<hr style="margin-top: 0;" />
|
|
73
|
-
|
|
74
72
|
<sortable-list data-id="${data.id}">
|
|
75
73
|
${rows.map((row) => {
|
|
76
74
|
const [name, struct] =
|
|
@@ -79,7 +77,7 @@ export default (props: {
|
|
|
79
77
|
if (!name || !struct) return html`<p>No name</p>`
|
|
80
78
|
|
|
81
79
|
return html`
|
|
82
|
-
|
|
80
|
+
<article data-id="${row.id}" data-signals="{ 'expanded-${row.id}': true }">
|
|
83
81
|
<header>
|
|
84
82
|
${struct.label}
|
|
85
83
|
|
|
@@ -88,19 +86,25 @@ export default (props: {
|
|
|
88
86
|
<input name="enable" type="checkbox" role="switch" checked />
|
|
89
87
|
</label> -->
|
|
90
88
|
|
|
89
|
+
<button data-on:click="$expanded-${row.id} = !$expanded-${row.id}" class="ghost handle text-secondary" data-tooltip="Collapse" data-placement="top">
|
|
90
|
+
${icons.minus}
|
|
91
|
+
</button>
|
|
92
|
+
|
|
91
93
|
<button
|
|
92
94
|
data-handle-for="${data.id}"
|
|
93
95
|
class="ghost handle text-secondary"
|
|
94
96
|
style="cursor: grab"
|
|
97
|
+
data-tooltip="Reorder"
|
|
98
|
+
data-placement="top"
|
|
95
99
|
>
|
|
96
100
|
${icons.bars}
|
|
97
101
|
</button>
|
|
98
102
|
|
|
99
103
|
<form
|
|
100
|
-
data-on
|
|
104
|
+
data-on:submit="@delete('/studio/api/block', {
|
|
101
105
|
contentType: 'form',
|
|
102
106
|
headers: {
|
|
103
|
-
render: 'Entry',
|
|
107
|
+
render: 'Entry LivePreview',
|
|
104
108
|
props: '${JSON.stringify({ entryId: entryId })}'
|
|
105
109
|
}
|
|
106
110
|
})"
|
|
@@ -120,14 +124,16 @@ export default (props: {
|
|
|
120
124
|
</aside>
|
|
121
125
|
</header>
|
|
122
126
|
|
|
123
|
-
${
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
<div data-show="$expanded-${row.id}">
|
|
128
|
+
${Render({
|
|
129
|
+
entryId,
|
|
130
|
+
parentId:
|
|
131
|
+
struct.instanceOf === BlockInstance ? row.id : data.id,
|
|
132
|
+
id: row.id,
|
|
133
|
+
structure: struct,
|
|
134
|
+
name: name,
|
|
135
|
+
})}
|
|
136
|
+
</div>
|
|
131
137
|
</article>
|
|
132
138
|
`
|
|
133
139
|
})}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { html } from 'hono/html'
|
|
2
|
-
import type {
|
|
2
|
+
import type { BlockDefStructure } from '../types.ts'
|
|
3
3
|
import Render from './Render.ts'
|
|
4
4
|
|
|
5
5
|
export default (props: {
|
|
6
|
-
entryId: number
|
|
7
|
-
parentId: number
|
|
6
|
+
entryId: number | string
|
|
7
|
+
parentId: number | string
|
|
8
8
|
id?: number
|
|
9
|
-
structure:
|
|
9
|
+
structure: BlockDefStructure
|
|
10
10
|
}) => {
|
|
11
11
|
const { entryId, parentId, structure } = props
|
|
12
12
|
|
package/components/Entries.ts
CHANGED
package/components/Entry.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { query } from '../queries/index.ts'
|
|
|
3
3
|
import { studioStructure } from '../index.ts'
|
|
4
4
|
import Render from './Render.ts'
|
|
5
5
|
|
|
6
|
-
export default (props: { entryId: number }) => {
|
|
6
|
+
export default (props: { entryId: number | string }) => {
|
|
7
7
|
const data = query.block({ id: props.entryId?.toString() })
|
|
8
8
|
|
|
9
9
|
if (!data) return html`<p>No entry with id: "${props.entryId}"</p>`
|
|
@@ -14,12 +14,18 @@ export default (props: { entryId: number }) => {
|
|
|
14
14
|
|
|
15
15
|
return html`
|
|
16
16
|
<div id="entry" class="entry">
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
<header>
|
|
18
|
+
<h1>Entry content</h1>
|
|
19
|
+
</header>
|
|
20
|
+
|
|
21
|
+
<div class="content">
|
|
22
|
+
${Render({
|
|
23
|
+
entryId: props.entryId,
|
|
24
|
+
parentId: props.entryId,
|
|
25
|
+
structure: structure,
|
|
26
|
+
name: data.name,
|
|
27
|
+
})}
|
|
28
|
+
</div>
|
|
23
29
|
</div>
|
|
24
30
|
|
|
25
31
|
`
|
|
@@ -2,10 +2,11 @@ import { Field } from './fields/index.ts'
|
|
|
2
2
|
import type { BlocksFieldDefStructure, FieldDefStructure } from '../types.ts'
|
|
3
3
|
import BlockFieldRenderer from './BlockFieldRenderer.ts'
|
|
4
4
|
import { BlockFieldInstance } from '../utils/define.ts'
|
|
5
|
+
import { html } from 'hono/html'
|
|
5
6
|
|
|
6
7
|
export default (props: {
|
|
7
|
-
entryId: number
|
|
8
|
-
parentId: number
|
|
8
|
+
entryId: number | string
|
|
9
|
+
parentId: number | string
|
|
9
10
|
structure: FieldDefStructure | BlocksFieldDefStructure
|
|
10
11
|
id?: number
|
|
11
12
|
name: string
|
|
@@ -18,23 +19,23 @@ export default (props: {
|
|
|
18
19
|
|
|
19
20
|
switch (structure.type) {
|
|
20
21
|
case 'text': {
|
|
21
|
-
return Field.Text({ entryId, parentId, name, id, structure })
|
|
22
|
+
return html`<div class="field">${Field.Text({ entryId, parentId, name, id, structure })}</div>`
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
case 'slug': {
|
|
25
|
-
return Field.Slug({ entryId, parentId, name, id, structure })
|
|
26
|
+
return html`<div class="field">${Field.Slug({ entryId, parentId, name, id, structure })}</div>`
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
case 'markdown': {
|
|
29
|
-
return Field.Markdown({ entryId, parentId, name, id, structure })
|
|
30
|
+
return html`<div class="field">${Field.Markdown({ entryId, parentId, name, id, structure })}</div>`
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
case 'image': {
|
|
33
|
-
return Field.Text({ entryId, parentId, name, structure, id })
|
|
34
|
+
return html`<div class="field">${Field.Text({ entryId, parentId, name, structure, id })}</div>`
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
case 'reference': {
|
|
37
|
-
return Field.Reference({ entryId, parentId, name, structure, id })
|
|
38
|
+
return html`<div class="field">${Field.Reference({ entryId, parentId, name, structure, id })}</div>`
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { html } from "hono/html"
|
|
2
|
+
import { query } from "../queries/index.ts"
|
|
3
|
+
import { studioConfig, studioStructure } from "../index.ts"
|
|
4
|
+
|
|
5
|
+
export default (props: { entryId: number | string }) => {
|
|
6
|
+
const data = query.root({ id: props.entryId })
|
|
7
|
+
|
|
8
|
+
if (!data) return html`<p>No entry with id: "${props.entryId}"</p>`
|
|
9
|
+
|
|
10
|
+
const structure = studioStructure[data.name]
|
|
11
|
+
|
|
12
|
+
if (!structure) return html`<p>No structure of type: ${data.name}</p>`
|
|
13
|
+
|
|
14
|
+
if (!structure.preview) return html``
|
|
15
|
+
|
|
16
|
+
const slugValue = 'slug' in structure.preview! && structure.preview?.slug
|
|
17
|
+
const slugColumn = 'field' in structure.preview! && structure.preview?.field
|
|
18
|
+
|
|
19
|
+
if (!slugValue && !slugColumn) return html``
|
|
20
|
+
|
|
21
|
+
let slug
|
|
22
|
+
|
|
23
|
+
if (slugValue) {
|
|
24
|
+
slug = slugValue
|
|
25
|
+
} else if (slugColumn) {
|
|
26
|
+
slug = data.fields[slugColumn].value
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!slug) return html``
|
|
30
|
+
|
|
31
|
+
return html`<live-preview id="live_preview" class="live-preview" data-rendered-at="${Date.now()}">
|
|
32
|
+
<header>
|
|
33
|
+
<h1>Live preview</h1>
|
|
34
|
+
</header>
|
|
35
|
+
<iframe src="http://localhost:${studioConfig.port}/${slug === '/' ? '' : slug}"></iframe>
|
|
36
|
+
</live-preview>`
|
|
37
|
+
}
|
package/components/Render.ts
CHANGED
|
@@ -15,8 +15,8 @@ import type {
|
|
|
15
15
|
} from '../types.ts'
|
|
16
16
|
|
|
17
17
|
export default (props: {
|
|
18
|
-
entryId: number
|
|
19
|
-
parentId: number
|
|
18
|
+
entryId: number | string
|
|
19
|
+
parentId: number | string
|
|
20
20
|
structure: BlockDefStructure | FieldDefStructure | BlocksFieldDefStructure
|
|
21
21
|
id?: number
|
|
22
22
|
name: string
|
package/components/SiteLayout.ts
CHANGED
|
@@ -25,10 +25,7 @@ export default (
|
|
|
25
25
|
|
|
26
26
|
<meta name="color-scheme" content="light dark" />
|
|
27
27
|
|
|
28
|
-
<script
|
|
29
|
-
type="module"
|
|
30
|
-
src="https://cdn.jsdelivr.net/gh/starfederation/datastar@main/bundles/datastar.js"
|
|
31
|
-
></script>
|
|
28
|
+
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-RC.6/bundles/datastar.js"></script>
|
|
32
29
|
|
|
33
30
|
<script type="importmap">
|
|
34
31
|
{
|
|
@@ -3,8 +3,8 @@ import { getOrCreateRow } from '../../utils/get-or-create-row.ts'
|
|
|
3
3
|
import { html } from '../../utils/html.ts'
|
|
4
4
|
|
|
5
5
|
export default (props: {
|
|
6
|
-
entryId: number
|
|
7
|
-
parentId: number
|
|
6
|
+
entryId: number | string
|
|
7
|
+
parentId: number | string
|
|
8
8
|
name: string
|
|
9
9
|
id?: number
|
|
10
10
|
structure: FieldDefStructure
|
|
@@ -17,7 +17,13 @@ export default (props: {
|
|
|
17
17
|
|
|
18
18
|
return html`
|
|
19
19
|
<form
|
|
20
|
-
data-on
|
|
20
|
+
data-on:input="@patch('/studio/api/block', {
|
|
21
|
+
contentType: 'form',
|
|
22
|
+
headers: {
|
|
23
|
+
render: 'LivePreview',
|
|
24
|
+
props: '${JSON.stringify({ entryId: entryId })}'
|
|
25
|
+
}
|
|
26
|
+
})"
|
|
21
27
|
>
|
|
22
28
|
<hgroup>
|
|
23
29
|
<label for="block-${data.id}">${structure.label}</label>
|
|
@@ -27,6 +33,7 @@ export default (props: {
|
|
|
27
33
|
<markdown-editor
|
|
28
34
|
data-content="${data.value?.trim()}"
|
|
29
35
|
data-id="${data.id}"
|
|
36
|
+
data-entry-id="${entryId}"
|
|
30
37
|
>
|
|
31
38
|
<!-- <textarea id="block-{data.id}" name="value" class="markdown">
|
|
32
39
|
{data.value}
|
|
@@ -4,8 +4,8 @@ import type { FieldDefStructure, ReferenceFieldStructure } from '../../types.ts'
|
|
|
4
4
|
import { query } from '../../queries/index.ts'
|
|
5
5
|
|
|
6
6
|
export default (props: {
|
|
7
|
-
entryId: number
|
|
8
|
-
parentId: number
|
|
7
|
+
entryId: number | string
|
|
8
|
+
parentId: number | string
|
|
9
9
|
name: string
|
|
10
10
|
id?: number
|
|
11
11
|
structure: ReferenceFieldStructure
|
|
@@ -23,14 +23,15 @@ export default (props: {
|
|
|
23
23
|
|
|
24
24
|
if (!data) return html`<p>No block</p>`
|
|
25
25
|
|
|
26
|
-
const entries = query.
|
|
26
|
+
const entries = query.blocks({ type: structure.to })
|
|
27
27
|
|
|
28
28
|
return html`
|
|
29
29
|
<form
|
|
30
|
-
data-on
|
|
30
|
+
data-on:input="@patch('/studio/api/block', {
|
|
31
31
|
contentType: 'form',
|
|
32
32
|
headers: {
|
|
33
|
-
render: ''
|
|
33
|
+
render: 'LivePreview',
|
|
34
|
+
props: '${JSON.stringify({ entryId: entryId })}'
|
|
34
35
|
}
|
|
35
36
|
})"
|
|
36
37
|
>
|
|
@@ -50,8 +51,11 @@ export default (props: {
|
|
|
50
51
|
/>
|
|
51
52
|
<datalist id="entries-${data.id}">
|
|
52
53
|
${entries.map((entry) => {
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
const slug = query.block({ name: 'slug', parent_id: entry.id })
|
|
55
|
+
const title = query.block({ name: 'title', parent_id: entry.id })
|
|
56
|
+
|
|
57
|
+
return html`<option value="${slug.value}">
|
|
58
|
+
${title.value}
|
|
55
59
|
</option>`
|
|
56
60
|
})}
|
|
57
61
|
</datalist>
|
|
@@ -43,8 +43,8 @@ app.get('/slug', async (c) => {
|
|
|
43
43
|
export const routes = app
|
|
44
44
|
|
|
45
45
|
export default (props: {
|
|
46
|
-
entryId: number
|
|
47
|
-
parentId: number
|
|
46
|
+
entryId: number | string
|
|
47
|
+
parentId: number | string
|
|
48
48
|
name: string
|
|
49
49
|
id?: number
|
|
50
50
|
structure: FieldDefStructure
|
|
@@ -69,10 +69,10 @@ export default (props: {
|
|
|
69
69
|
return html`
|
|
70
70
|
<div style="display: flex; align-items: center">
|
|
71
71
|
<form
|
|
72
|
-
data-on
|
|
72
|
+
data-on:change="@patch('/studio/api/block', {
|
|
73
73
|
contentType: 'form',
|
|
74
74
|
headers: {
|
|
75
|
-
render: 'Entry',
|
|
75
|
+
render: 'Entry LivePreview',
|
|
76
76
|
props: '${JSON.stringify({ entryId: entryId })}'
|
|
77
77
|
}
|
|
78
78
|
})"
|
|
@@ -98,10 +98,10 @@ export default (props: {
|
|
|
98
98
|
|
|
99
99
|
<form
|
|
100
100
|
style="margin-top: 21px"
|
|
101
|
-
data-on
|
|
101
|
+
data-on:submit="@patch('/studio/api/block', {
|
|
102
102
|
contentType: 'form',
|
|
103
103
|
headers: {
|
|
104
|
-
render: 'Entry',
|
|
104
|
+
render: 'Entry LivePreview',
|
|
105
105
|
props: '${JSON.stringify({ entryId: entryId })}'
|
|
106
106
|
}
|
|
107
107
|
})"
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { getOrCreateRow } from '../../utils/get-or-create-row.ts'
|
|
2
2
|
import { html } from '../../utils/html.ts'
|
|
3
3
|
import type { FieldDefStructure } from '../../types.ts'
|
|
4
|
+
import { raw } from 'hono/html'
|
|
4
5
|
|
|
5
6
|
export default (props: {
|
|
6
|
-
entryId: number
|
|
7
|
-
parentId: number
|
|
7
|
+
entryId: number | string
|
|
8
|
+
parentId: number | string
|
|
8
9
|
name: string
|
|
9
10
|
id?: number
|
|
10
11
|
structure: FieldDefStructure
|
|
@@ -26,21 +27,25 @@ export default (props: {
|
|
|
26
27
|
|
|
27
28
|
return html`
|
|
28
29
|
<form
|
|
29
|
-
|
|
30
|
+
class="field-text"
|
|
31
|
+
data-on:input="@patch('/studio/api/block', {
|
|
30
32
|
contentType: 'form',
|
|
31
33
|
headers: {
|
|
32
|
-
render: '${isEntryTitle ? 'AdminPanel' : ''}'
|
|
34
|
+
render: '${isEntryTitle ? 'AdminPanel' : ''} LivePreview',
|
|
35
|
+
props: '${JSON.stringify({ entryId: entryId })}'
|
|
33
36
|
}
|
|
34
37
|
})"
|
|
35
38
|
>
|
|
36
39
|
<hgroup>
|
|
37
40
|
<label for="block-${data.id}">${structure.label}</label>
|
|
38
|
-
${structure.description
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
${structure.description ?
|
|
42
|
+
html`
|
|
43
|
+
<p><small>${structure.description}</small></p>
|
|
44
|
+
` : null}
|
|
42
45
|
</hgroup>
|
|
43
46
|
|
|
47
|
+
${structure.presentation === 'svg' ? html`<output>${raw(data.value)}</output>`: ''}
|
|
48
|
+
|
|
44
49
|
<input
|
|
45
50
|
id="block-${data.id}"
|
|
46
51
|
name="value"
|
package/components/icons.ts
CHANGED
|
@@ -249,3 +249,6 @@ export const download = html`
|
|
|
249
249
|
</g>
|
|
250
250
|
</svg>
|
|
251
251
|
`
|
|
252
|
+
|
|
253
|
+
export const minus = html`
|
|
254
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 20 20"><!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE --><path fill="currentColor" d="M6.75 9.25a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5z"/></svg>`
|
|
@@ -33,7 +33,7 @@ export default () => {
|
|
|
33
33
|
<td><input type="text" disabled value="${apiKey.hint}" /></td>
|
|
34
34
|
<td>
|
|
35
35
|
<form
|
|
36
|
-
data-on
|
|
36
|
+
data-on:submit="@delete('/studio/api/api-key', {
|
|
37
37
|
contentType: 'form',
|
|
38
38
|
headers: {
|
|
39
39
|
render: 'Settings'
|
|
@@ -58,7 +58,7 @@ export default () => {
|
|
|
58
58
|
</table>
|
|
59
59
|
|
|
60
60
|
<form
|
|
61
|
-
data-on
|
|
61
|
+
data-on:submit="@post('/studio/api/api-key', {
|
|
62
62
|
contentType: 'form',
|
|
63
63
|
headers: {
|
|
64
64
|
render: 'Settings'
|
|
@@ -93,7 +93,7 @@ export default () => {
|
|
|
93
93
|
<button
|
|
94
94
|
style="display: flex; align-items: center;"
|
|
95
95
|
data-attr="{ id: $apiKey }"
|
|
96
|
-
data-on
|
|
96
|
+
data-on:click="navigator.clipboard.writeText($apiKey); $copied = true"
|
|
97
97
|
class="ghost"
|
|
98
98
|
aria-label="Copy key to clipboard"
|
|
99
99
|
>
|
|
@@ -110,7 +110,7 @@ export default () => {
|
|
|
110
110
|
<footer>
|
|
111
111
|
<button
|
|
112
112
|
class="ghost"
|
|
113
|
-
data-on
|
|
113
|
+
data-on:click="$apiKey = ''; $copied = false; evt.target.closest('dialog')?.close()"
|
|
114
114
|
>
|
|
115
115
|
Close
|
|
116
116
|
</button>
|
|
@@ -41,7 +41,7 @@ export default async () => {
|
|
|
41
41
|
</th>
|
|
42
42
|
<th>
|
|
43
43
|
<form
|
|
44
|
-
data-on
|
|
44
|
+
data-on:submit="@delete('/studio/api/backup', {
|
|
45
45
|
contentType: 'form',
|
|
46
46
|
headers: {
|
|
47
47
|
render: 'Settings'
|
|
@@ -59,7 +59,7 @@ export default async () => {
|
|
|
59
59
|
</table>
|
|
60
60
|
|
|
61
61
|
<form
|
|
62
|
-
data-on
|
|
62
|
+
data-on:submit="@post('/studio/api/backup', {
|
|
63
63
|
contentType: 'form',
|
|
64
64
|
headers: {
|
|
65
65
|
render: 'Settings'
|
|
@@ -72,7 +72,7 @@ export default async () => {
|
|
|
72
72
|
<hr />
|
|
73
73
|
|
|
74
74
|
<form
|
|
75
|
-
data-on
|
|
75
|
+
data-on:submit="@post('/studio/api/backup', { contentType: 'form' })"
|
|
76
76
|
>
|
|
77
77
|
<input type="file" name="file" />
|
|
78
78
|
<button type="submit" class="ghost">Restore database</button>
|
|
@@ -38,7 +38,7 @@ export default () => {
|
|
|
38
38
|
<article>
|
|
39
39
|
<header>Register user</header>
|
|
40
40
|
<form
|
|
41
|
-
data-on
|
|
41
|
+
data-on:submit="@post('/studio/api/auth/register', { contentType: 'form' })"
|
|
42
42
|
>
|
|
43
43
|
<label for="register_email"><small>Email</small></label>
|
|
44
44
|
<input
|
package/index.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { serveStatic } from '@hono/node-server/serve-static'
|
|
|
6
6
|
import { HTTPException } from 'hono/http-exception'
|
|
7
7
|
|
|
8
8
|
import { loadDb } from '@alstar/db'
|
|
9
|
-
import { createRefresher } from '@alstar/refresher'
|
|
10
9
|
|
|
11
10
|
import { createStudioTables } from './utils/create-studio-tables.ts'
|
|
12
11
|
import { fileBasedRouter } from './utils/file-based-router.ts'
|
|
@@ -20,21 +19,20 @@ import auth from './utils/auth.ts'
|
|
|
20
19
|
import ErrorPage from './pages/error.ts'
|
|
21
20
|
|
|
22
21
|
import * as types from './types.ts'
|
|
22
|
+
import { refresher, refreshClient } from './utils/refresher.ts'
|
|
23
23
|
|
|
24
24
|
export let rootdir = './node_modules/@alstar/studio'
|
|
25
25
|
|
|
26
26
|
export let studioStructure: types.Structure = {}
|
|
27
27
|
export let studioConfig: types.StudioConfig = {
|
|
28
28
|
siteName: '',
|
|
29
|
+
honoConfig: {},
|
|
29
30
|
fileBasedRouter: true,
|
|
30
31
|
port: 3000,
|
|
31
32
|
structure: {},
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
const createStudio = async (config: types.
|
|
35
|
-
// const refresher = await createRefresher({ rootdir: ['.', import.meta.dirname] })
|
|
36
|
-
const refresher = await createRefresher({ rootdir: '.' })
|
|
37
|
-
|
|
35
|
+
const createStudio = async (config: types.StudioConfigInput) => {
|
|
38
36
|
loadDb('./studio.db')
|
|
39
37
|
createStudioTables()
|
|
40
38
|
|
|
@@ -48,6 +46,8 @@ const createStudio = async (config: types.StudioConfig) => {
|
|
|
48
46
|
|
|
49
47
|
const app = new Hono(studioConfig.honoConfig)
|
|
50
48
|
|
|
49
|
+
app.get('/refresh', refresher({ root: '.', exclude: '.db' }))
|
|
50
|
+
|
|
51
51
|
/**
|
|
52
52
|
* Static folders
|
|
53
53
|
*/
|
|
@@ -104,8 +104,6 @@ const createStudio = async (config: types.StudioConfig) => {
|
|
|
104
104
|
}),
|
|
105
105
|
)
|
|
106
106
|
|
|
107
|
-
// console.log(app.routes)
|
|
108
|
-
|
|
109
107
|
/**
|
|
110
108
|
* Run server
|
|
111
109
|
*/
|
|
@@ -129,9 +127,12 @@ const createStudio = async (config: types.StudioConfig) => {
|
|
|
129
127
|
})
|
|
130
128
|
})
|
|
131
129
|
|
|
132
|
-
startupLog({ port: studioConfig.port
|
|
130
|
+
startupLog({ port: studioConfig.port })
|
|
133
131
|
|
|
134
|
-
return
|
|
132
|
+
return {
|
|
133
|
+
app,
|
|
134
|
+
refreshClient: refreshClient(studioConfig.port)
|
|
135
|
+
}
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
export {
|
|
@@ -144,6 +145,6 @@ export {
|
|
|
144
145
|
} from './utils/define.ts'
|
|
145
146
|
export { type RequestContext } from './types.ts'
|
|
146
147
|
export { createStudio }
|
|
147
|
-
export { html, type HtmlEscapedString } from './utils/html.ts'
|
|
148
|
+
export { html, raw, type HtmlEscapedString } from './utils/html.ts'
|
|
148
149
|
export { query } from './queries/index.ts'
|
|
149
150
|
export const version = packageJSON.version
|