@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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { db } from '@alstar/db'
|
|
2
|
+
import { html } from 'hono/html'
|
|
3
|
+
import { sql } from '../utils/sql.ts'
|
|
4
|
+
import * as icons from './icons.ts'
|
|
5
|
+
|
|
6
|
+
export default () => {
|
|
7
|
+
const apiKeys = db.database
|
|
8
|
+
.prepare(sql`
|
|
9
|
+
select
|
|
10
|
+
*
|
|
11
|
+
from
|
|
12
|
+
api_keys
|
|
13
|
+
`)
|
|
14
|
+
.all()
|
|
15
|
+
|
|
16
|
+
return html`
|
|
17
|
+
<div id="settings" data-signals="{ apiKey: '', copied: false }">
|
|
18
|
+
<!-- <h1>Settings</h1> -->
|
|
19
|
+
<article>
|
|
20
|
+
<header>API Keys</header>
|
|
21
|
+
|
|
22
|
+
<ul>
|
|
23
|
+
${apiKeys.map((apiKey) => {
|
|
24
|
+
return html`<li>
|
|
25
|
+
<p>${apiKey.name}</p>
|
|
26
|
+
<input type="text" disabled value="${apiKey.hint}" />
|
|
27
|
+
<form
|
|
28
|
+
data-on-submit="@delete('/admin/api/api-key', { contentType: 'form' })"
|
|
29
|
+
>
|
|
30
|
+
<button
|
|
31
|
+
data-tooltip="Delete API key"
|
|
32
|
+
data-placement="left"
|
|
33
|
+
type="submit"
|
|
34
|
+
class="ghost"
|
|
35
|
+
>
|
|
36
|
+
${icons.trash}
|
|
37
|
+
</button>
|
|
38
|
+
|
|
39
|
+
<input type="hidden" name="value" value="${apiKey.value}" />
|
|
40
|
+
</form>
|
|
41
|
+
</li>`
|
|
42
|
+
})}
|
|
43
|
+
</ul>
|
|
44
|
+
|
|
45
|
+
<form
|
|
46
|
+
data-on-submit="@post('/admin/api/api-key', { contentType: 'form' })"
|
|
47
|
+
>
|
|
48
|
+
<label for="api_key_name">Generate API Key</label>
|
|
49
|
+
|
|
50
|
+
<input
|
|
51
|
+
data-bind="name"
|
|
52
|
+
type="text"
|
|
53
|
+
name="name"
|
|
54
|
+
id="api_key_name"
|
|
55
|
+
placeholder="Name"
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
<button type="submit" class="ghost">Generate key</button>
|
|
59
|
+
</form>
|
|
60
|
+
|
|
61
|
+
<dialog data-attr="{ open: $apiKey !== '' }">
|
|
62
|
+
<article>
|
|
63
|
+
<header>
|
|
64
|
+
<p>API Key</p>
|
|
65
|
+
</header>
|
|
66
|
+
<p>Be sure to save this key, as it wont be shown again.</p>
|
|
67
|
+
|
|
68
|
+
<div style="display: flex; gap: 1rem; align-items: center;">
|
|
69
|
+
<h3 style="margin: 0;">
|
|
70
|
+
<code data-text="$apiKey"></code>
|
|
71
|
+
</h3>
|
|
72
|
+
|
|
73
|
+
<button
|
|
74
|
+
style="display: flex; align-items: center;"
|
|
75
|
+
data-attr="{ id: $apiKey }"
|
|
76
|
+
data-on-click="navigator.clipboard.writeText($apiKey); $copied = true"
|
|
77
|
+
class="ghost"
|
|
78
|
+
aria-label="Copy key to clipboard"
|
|
79
|
+
>
|
|
80
|
+
${icons.clipboard}
|
|
81
|
+
<span style="display: none; margin-left: 0.5rem; color: green;" data-style="{ display: $copied && 'block' }">Copied</span>
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<footer>
|
|
86
|
+
<button
|
|
87
|
+
class="ghost"
|
|
88
|
+
data-on-click="$apiKey = ''; $copied = false; evt.target.closest('dialog')?.close()"
|
|
89
|
+
>
|
|
90
|
+
Close
|
|
91
|
+
</button>
|
|
92
|
+
</footer>
|
|
93
|
+
</article>
|
|
94
|
+
</dialog>
|
|
95
|
+
</article>
|
|
96
|
+
</div>
|
|
97
|
+
`
|
|
98
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { query } from '../../queries/index.ts'
|
|
2
|
+
import type { Field, Block as BlockType, BlocksField } from '../../types.ts'
|
|
3
|
+
import { getOrCreateRow } from '../../utils/get-or-create-row.ts'
|
|
4
|
+
import { html } from '../../utils/html.ts'
|
|
5
|
+
import Block from '../Block.ts'
|
|
6
|
+
import * as icons from '../icons.ts'
|
|
7
|
+
|
|
8
|
+
export default (props: {
|
|
9
|
+
entryId: number
|
|
10
|
+
parentId: number
|
|
11
|
+
name: string
|
|
12
|
+
field: BlocksField
|
|
13
|
+
sortOrder: number
|
|
14
|
+
}) => {
|
|
15
|
+
const { entryId, parentId, name, field, sortOrder = 0 } = props
|
|
16
|
+
|
|
17
|
+
const data = getOrCreateRow(parentId, name, field, sortOrder)
|
|
18
|
+
|
|
19
|
+
if (!data) return html`<p>No block</p>`
|
|
20
|
+
|
|
21
|
+
const entries = Object.entries(field.children) as [
|
|
22
|
+
string,
|
|
23
|
+
BlockType | Field,
|
|
24
|
+
][]
|
|
25
|
+
const rows = query.blocks({ parent_id: data.id })
|
|
26
|
+
|
|
27
|
+
return html`
|
|
28
|
+
<div class="blocks-field">
|
|
29
|
+
<header>
|
|
30
|
+
<p>${field.label}</p>
|
|
31
|
+
|
|
32
|
+
<details class="dropdown">
|
|
33
|
+
<summary>Add</summary>
|
|
34
|
+
<ul>
|
|
35
|
+
${entries.map(([name, block]) => {
|
|
36
|
+
return html`
|
|
37
|
+
<li>
|
|
38
|
+
<form
|
|
39
|
+
data-on-submit="@post('/admin/api/new-block', { contentType: 'form' })"
|
|
40
|
+
>
|
|
41
|
+
<button type="submit" class="ghost">${block.label}</button>
|
|
42
|
+
<input type="hidden" name="type" value="${block.type}" />
|
|
43
|
+
<input type="hidden" name="name" value="${name}" />
|
|
44
|
+
<input type="hidden" name="label" value="${block.label}" />
|
|
45
|
+
<input type="hidden" name="parent_id" value="${data.id}" />
|
|
46
|
+
<input type="hidden" name="entry_id" value="${entryId}" />
|
|
47
|
+
<input
|
|
48
|
+
type="hidden"
|
|
49
|
+
name="sort_order"
|
|
50
|
+
value="${rows.length}"
|
|
51
|
+
/>
|
|
52
|
+
</form>
|
|
53
|
+
</li>
|
|
54
|
+
`
|
|
55
|
+
})}
|
|
56
|
+
</ul>
|
|
57
|
+
</details>
|
|
58
|
+
</header>
|
|
59
|
+
|
|
60
|
+
<hr style="margin-top: 0;" />
|
|
61
|
+
|
|
62
|
+
<div data-sortable="${data.id}">
|
|
63
|
+
${rows.map((row, idx) => {
|
|
64
|
+
const [name, struct] = (entries.find(([name]) => name === row.name) ||
|
|
65
|
+
[]) as [string, BlockType]
|
|
66
|
+
|
|
67
|
+
if (!name) return html``
|
|
68
|
+
|
|
69
|
+
return html`
|
|
70
|
+
<article>
|
|
71
|
+
<header>
|
|
72
|
+
${struct.label}
|
|
73
|
+
|
|
74
|
+
<!-- data-tooltip="Move"
|
|
75
|
+
data-placement="top" -->
|
|
76
|
+
<aside>
|
|
77
|
+
<button
|
|
78
|
+
data-handle="${data.id}"
|
|
79
|
+
class="ghost handle text-secondary"
|
|
80
|
+
style="cursor: grab"
|
|
81
|
+
>
|
|
82
|
+
${icons.bars}
|
|
83
|
+
</button>
|
|
84
|
+
|
|
85
|
+
<form
|
|
86
|
+
data-on-submit="@delete('/admin/api/block', { contentType: 'form' })"
|
|
87
|
+
>
|
|
88
|
+
<button
|
|
89
|
+
type="submit"
|
|
90
|
+
class="ghost text-secondary"
|
|
91
|
+
data-tooltip="Remove"
|
|
92
|
+
data-placement="top"
|
|
93
|
+
aria-label="Delete block"
|
|
94
|
+
>
|
|
95
|
+
${icons.x}
|
|
96
|
+
</button>
|
|
97
|
+
<input type="hidden" name="id" value="${row.id}" />
|
|
98
|
+
<input type="hidden" name="entry_id" value="${entryId}" />
|
|
99
|
+
</form>
|
|
100
|
+
</aside>
|
|
101
|
+
</header>
|
|
102
|
+
|
|
103
|
+
${Block({
|
|
104
|
+
entryId,
|
|
105
|
+
parentId: struct.fields ? row.id : data.id,
|
|
106
|
+
blockStructure: struct,
|
|
107
|
+
name: name,
|
|
108
|
+
sortOrder: row.sort_order,
|
|
109
|
+
})}
|
|
110
|
+
|
|
111
|
+
<!-- <pre><code>{JSON.stringify(struct, null, 2)} - {row.sort_order}</code></pre> -->
|
|
112
|
+
</article>
|
|
113
|
+
`
|
|
114
|
+
})}
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
`
|
|
118
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { FieldDef } from '../../types.ts'
|
|
2
|
+
import { getOrCreateRow } from '../../utils/get-or-create-row.ts'
|
|
3
|
+
import { html } from '../../utils/html.ts'
|
|
4
|
+
|
|
5
|
+
export default (props: {
|
|
6
|
+
entryId: number
|
|
7
|
+
parentId: number
|
|
8
|
+
name: string
|
|
9
|
+
field: FieldDef
|
|
10
|
+
sortOrder: number
|
|
11
|
+
}) => {
|
|
12
|
+
const { entryId, parentId, name, field, sortOrder = 0 } = props
|
|
13
|
+
|
|
14
|
+
const data = getOrCreateRow(parentId, name, field, sortOrder)
|
|
15
|
+
|
|
16
|
+
if (!data) return html`<p>No block</p>`
|
|
17
|
+
|
|
18
|
+
return html`
|
|
19
|
+
<form
|
|
20
|
+
data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
|
|
21
|
+
>
|
|
22
|
+
<hgroup>
|
|
23
|
+
<label for="block-${data.id}">${field.label}</label>
|
|
24
|
+
<p><small>${field.description}</small></p>
|
|
25
|
+
</hgroup>
|
|
26
|
+
|
|
27
|
+
<input
|
|
28
|
+
id="block-${data.id}"
|
|
29
|
+
name="value"
|
|
30
|
+
type="text"
|
|
31
|
+
value="${data.value}"
|
|
32
|
+
/>
|
|
33
|
+
|
|
34
|
+
<input type="hidden" name="type" value="${field.type}" />
|
|
35
|
+
<input type="hidden" name="id" value="${data.id}" />
|
|
36
|
+
<input type="hidden" name="entryId" value="${entryId}" />
|
|
37
|
+
<input type="hidden" name="parentId" value="${parentId}" />
|
|
38
|
+
<input type="hidden" name="sort_order" value="${sortOrder}" />
|
|
39
|
+
<input type="hidden" name="name" value="${name}" />
|
|
40
|
+
</form>
|
|
41
|
+
`
|
|
42
|
+
}
|
package/components/icons.ts
CHANGED
|
@@ -135,3 +135,62 @@ export const trash = html`
|
|
|
135
135
|
/>
|
|
136
136
|
</svg>
|
|
137
137
|
`
|
|
138
|
+
|
|
139
|
+
export const x = html`
|
|
140
|
+
<svg
|
|
141
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
142
|
+
width="26"
|
|
143
|
+
height="26"
|
|
144
|
+
viewBox="0 0 24 24"
|
|
145
|
+
>
|
|
146
|
+
<!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
|
|
147
|
+
<path
|
|
148
|
+
fill="none"
|
|
149
|
+
stroke="currentColor"
|
|
150
|
+
stroke-linecap="round"
|
|
151
|
+
stroke-linejoin="round"
|
|
152
|
+
stroke-width="1.5"
|
|
153
|
+
d="M6 18L18 6M6 6l12 12"
|
|
154
|
+
/>
|
|
155
|
+
</svg>
|
|
156
|
+
`
|
|
157
|
+
|
|
158
|
+
export const cog = html`<svg
|
|
159
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
160
|
+
width="16"
|
|
161
|
+
height="16"
|
|
162
|
+
viewBox="0 0 24 24"
|
|
163
|
+
>
|
|
164
|
+
<!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
|
|
165
|
+
<g
|
|
166
|
+
fill="none"
|
|
167
|
+
stroke="currentColor"
|
|
168
|
+
stroke-linecap="round"
|
|
169
|
+
stroke-linejoin="round"
|
|
170
|
+
stroke-width="1.5"
|
|
171
|
+
>
|
|
172
|
+
<path
|
|
173
|
+
d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93c.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 0 1 1.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204s.505.71.93.78l.893.15c.543.09.94.559.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.894.149c-.424.07-.764.383-.929.78c-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 0 1-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107c-.398.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93c-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 0 1-.12-1.45l.527-.737c.25-.35.272-.806.108-1.204s-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78c.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 0 1 .12-1.45l.773-.773a1.125 1.125 0 0 1 1.45-.12l.737.527c.35.25.807.272 1.204.107s.71-.505.78-.929z"
|
|
174
|
+
/>
|
|
175
|
+
<path d="M15 12a3 3 0 1 1-6 0a3 3 0 0 1 6 0" />
|
|
176
|
+
</g>
|
|
177
|
+
</svg>`
|
|
178
|
+
|
|
179
|
+
export const clipboard = html`
|
|
180
|
+
<svg
|
|
181
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
182
|
+
width="16"
|
|
183
|
+
height="16"
|
|
184
|
+
viewBox="0 0 24 24"
|
|
185
|
+
>
|
|
186
|
+
<!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
|
|
187
|
+
<path
|
|
188
|
+
fill="none"
|
|
189
|
+
stroke="currentColor"
|
|
190
|
+
stroke-linecap="round"
|
|
191
|
+
stroke-linejoin="round"
|
|
192
|
+
stroke-width="1.5"
|
|
193
|
+
d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0q.083.292.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0q.002-.32.084-.612m7.332 0q.969.073 1.927.184c1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48 48 0 0 1 1.927-.184"
|
|
194
|
+
/>
|
|
195
|
+
</svg>
|
|
196
|
+
`
|
package/components/index.ts
CHANGED
package/components/layout.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import adminPanel from './AdminPanel
|
|
1
|
+
import adminPanel from './AdminPanel.ts'
|
|
2
2
|
import { html } from 'hono/html'
|
|
3
3
|
import { type HtmlEscapedString } from 'hono/utils/html'
|
|
4
4
|
import { rootdir, studioConfig } from '../index.ts'
|
|
@@ -50,7 +50,7 @@ export default (props: {
|
|
|
50
50
|
</head>
|
|
51
51
|
|
|
52
52
|
<body data-barba="wrapper">
|
|
53
|
-
<section>${adminPanel(props.structure)}</section>
|
|
53
|
+
<section style="margin-bottom: 0;">${adminPanel(props.structure)}</section>
|
|
54
54
|
|
|
55
55
|
<main>
|
|
56
56
|
<section data-barba="container" data-barba-namespace="default">
|
package/index.ts
CHANGED
|
@@ -1,33 +1,45 @@
|
|
|
1
1
|
import { Hono } from 'hono'
|
|
2
2
|
import { loadDb } from '@alstar/db'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
3
|
+
import { serve } from '@hono/node-server'
|
|
4
|
+
import { serveStatic } from '@hono/node-server/serve-static'
|
|
5
|
+
import { createRefresher } from '@alstar/refresher'
|
|
6
6
|
|
|
7
7
|
import Layout from './components/layout.ts'
|
|
8
8
|
import IndexPage from './components/index.ts'
|
|
9
|
+
import SettingsPage from './components/Settings.ts'
|
|
9
10
|
import Entry from './components/Entry.ts'
|
|
10
11
|
|
|
11
|
-
import
|
|
12
|
-
import { serveStatic } from '@hono/node-server/serve-static'
|
|
12
|
+
import * as types from './types.ts'
|
|
13
13
|
import { createStudioTables } from './utils/create-studio-tables.ts'
|
|
14
14
|
import { fileBasedRouter } from './utils/file-based-router.ts'
|
|
15
|
+
import { getConfig } from './utils/get-config.ts'
|
|
16
|
+
import startupLog from './utils/startup-log.ts'
|
|
17
|
+
import { api } from './api/index.ts'
|
|
18
|
+
import mcp from './api/mcp.ts'
|
|
15
19
|
|
|
16
20
|
export let structure: types.Structure
|
|
17
21
|
export let rootdir = '/node_modules/@alstar/studio'
|
|
18
22
|
|
|
19
23
|
export let studioConfig: types.StudioConfig = {
|
|
20
24
|
siteName: '',
|
|
25
|
+
structure: {}
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
const createStudio = async (
|
|
28
|
+
const createStudio = async (config: types.StudioConfig) => {
|
|
29
|
+
startupLog()
|
|
30
|
+
|
|
31
|
+
createRefresher({ rootdir: '.' })
|
|
32
|
+
|
|
24
33
|
loadDb('./studio.db')
|
|
25
34
|
createStudioTables()
|
|
26
35
|
|
|
27
|
-
const configFile = await getConfig<types.StudioConfig>()
|
|
36
|
+
// const configFile = await getConfig<types.StudioConfig>()
|
|
37
|
+
|
|
38
|
+
if (config.structure) {
|
|
39
|
+
structure = config.structure
|
|
40
|
+
}
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
studioConfig = { ...studioConfig, ...configFile }
|
|
42
|
+
studioConfig = { ...studioConfig, ...config }
|
|
31
43
|
|
|
32
44
|
const app = new Hono(studioConfig.honoConfig)
|
|
33
45
|
|
|
@@ -35,18 +47,19 @@ const createStudio = async (studioStructure?: types.Structure) => {
|
|
|
35
47
|
app.use('*', serveStatic({ root: './public' }))
|
|
36
48
|
|
|
37
49
|
app.get('/admin', (c) => c.html(Layout({ structure, content: IndexPage() })))
|
|
38
|
-
|
|
39
|
-
app.route('/admin/api', sectionRoutes(structure))
|
|
40
|
-
|
|
50
|
+
app.get('/admin/settings', (c) => c.html(Layout({ structure, content: SettingsPage() })))
|
|
41
51
|
app.get('/admin/entry/:id', (c) => {
|
|
42
52
|
return c.html(
|
|
43
53
|
Layout({
|
|
44
54
|
structure,
|
|
45
|
-
content: Entry({
|
|
55
|
+
content: Entry({ entryId: parseInt(c.req.param('id')) }),
|
|
46
56
|
}),
|
|
47
57
|
)
|
|
48
58
|
})
|
|
49
59
|
|
|
60
|
+
app.route('/admin/api', api(structure))
|
|
61
|
+
app.route('/admin/mcp', mcp())
|
|
62
|
+
|
|
50
63
|
const pages = await fileBasedRouter('./pages')
|
|
51
64
|
|
|
52
65
|
if (pages) {
|
|
@@ -73,7 +86,13 @@ const createStudio = async (studioStructure?: types.Structure) => {
|
|
|
73
86
|
return app
|
|
74
87
|
}
|
|
75
88
|
|
|
76
|
-
export {
|
|
89
|
+
export {
|
|
90
|
+
defineConfig,
|
|
91
|
+
defineEntry,
|
|
92
|
+
defineStructure,
|
|
93
|
+
defineBlock,
|
|
94
|
+
defineField,
|
|
95
|
+
} from './utils/define.ts'
|
|
77
96
|
export { type RequestContext } from './types.ts'
|
|
78
97
|
export { createStudio }
|
|
79
98
|
export { html, type HtmlEscapedString } from './utils/html.ts'
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alstar/studio",
|
|
3
|
-
"version": "0.0.0-beta.
|
|
3
|
+
"version": "0.0.0-beta.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@hono/node-server": "^1.18.1",
|
|
8
8
|
"@starfederation/datastar-sdk": "1.0.0-RC.1",
|
|
9
9
|
"hono": "^4.8.12",
|
|
10
|
-
"@alstar/
|
|
11
|
-
"@alstar/
|
|
10
|
+
"@alstar/db": "0.0.0-beta.1",
|
|
11
|
+
"@alstar/refresher": "0.0.0-beta.2",
|
|
12
|
+
"@alstar/ui": "0.0.0-beta.1"
|
|
12
13
|
},
|
|
13
14
|
"devDependencies": {
|
|
14
15
|
"@types/node": "^24.1.0",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
.admin-panel {
|
|
2
|
+
/* background: hsla(0, 0%, 0%, 0.1); */
|
|
3
|
+
padding: 40px;
|
|
4
|
+
margin-bottom: 0;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: flex-start;
|
|
8
|
+
|
|
9
|
+
height: 100%;
|
|
10
|
+
min-height: inherit;
|
|
11
|
+
|
|
12
|
+
min-width: 250px;
|
|
13
|
+
|
|
14
|
+
> h1 {
|
|
15
|
+
padding-bottom: 1rem;
|
|
16
|
+
|
|
17
|
+
a {
|
|
18
|
+
display: flex;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
svg {
|
|
22
|
+
height: 1.6rem;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
form {
|
|
27
|
+
padding-bottom: 1rem;
|
|
28
|
+
|
|
29
|
+
button {
|
|
30
|
+
margin: 10px 0px 20px;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#entries {
|
|
35
|
+
width: 100%;
|
|
36
|
+
|
|
37
|
+
ul {
|
|
38
|
+
padding: 0;
|
|
39
|
+
margin-inline: -1rem;
|
|
40
|
+
|
|
41
|
+
form {
|
|
42
|
+
display: flex;
|
|
43
|
+
padding-bottom: 0;
|
|
44
|
+
|
|
45
|
+
button {
|
|
46
|
+
margin: 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
> li {
|
|
51
|
+
margin-bottom: 0px;
|
|
52
|
+
border-radius: 8px;
|
|
53
|
+
display: flex;
|
|
54
|
+
justify-content: space-between;
|
|
55
|
+
align-items: stretch;
|
|
56
|
+
list-style: none;
|
|
57
|
+
|
|
58
|
+
a {
|
|
59
|
+
text-decoration: none;
|
|
60
|
+
width: 100%;
|
|
61
|
+
padding: 0.5rem 1rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
button {
|
|
65
|
+
border-radius: 7px;
|
|
66
|
+
opacity: 0;
|
|
67
|
+
transition: opacity 100px;
|
|
68
|
+
|
|
69
|
+
svg {
|
|
70
|
+
margin: 0.5rem 1rem;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
> footer {
|
|
78
|
+
margin-top: auto;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#entries ul {
|
|
83
|
+
> li:hover {
|
|
84
|
+
background-color: var(--pico-form-element-background-color);
|
|
85
|
+
|
|
86
|
+
button {
|
|
87
|
+
opacity: 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.blocks-field {
|
|
2
|
+
> header {
|
|
3
|
+
display: flex;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
align-items: center;
|
|
6
|
+
|
|
7
|
+
> form fieldset {
|
|
8
|
+
button {
|
|
9
|
+
padding-inline: 1rem;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
details {
|
|
14
|
+
form {
|
|
15
|
+
display: flex;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
button {
|
|
19
|
+
width: 100%;
|
|
20
|
+
margin-bottom: 0;
|
|
21
|
+
text-align: left;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
summary + ul {
|
|
25
|
+
right: 0;
|
|
26
|
+
left: auto;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
article {
|
|
32
|
+
> header {
|
|
33
|
+
display: flex;
|
|
34
|
+
justify-content: space-between;
|
|
35
|
+
align-items: center;
|
|
36
|
+
|
|
37
|
+
aside {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: 5px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
button {
|
|
44
|
+
margin: 0;
|
|
45
|
+
padding: 0;
|
|
46
|
+
|
|
47
|
+
svg {
|
|
48
|
+
padding: 5px;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
package/public/main.css
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/* @import './../node_modules/@alstar/ui/red.css'; */
|
|
2
2
|
@import 'https://esm.sh/@alstar/ui/red.css';
|
|
3
|
+
@import './admin-panel.css';
|
|
4
|
+
@import './entry.css';
|
|
5
|
+
@import './blocks.css';
|
|
6
|
+
@import './settings.css';
|
|
3
7
|
|
|
4
8
|
body {
|
|
5
9
|
min-height: 100vh;
|
|
@@ -93,3 +97,7 @@ div.ink-mde {
|
|
|
93
97
|
display: flex;
|
|
94
98
|
justify-content: center;
|
|
95
99
|
}
|
|
100
|
+
|
|
101
|
+
.text-secondary {
|
|
102
|
+
color: var(--pico-secondary);
|
|
103
|
+
}
|
package/public/main.js
CHANGED
|
@@ -3,6 +3,7 @@ import Sortable from 'sortablejs'
|
|
|
3
3
|
// import { wrap } from 'ink-mde'
|
|
4
4
|
|
|
5
5
|
barba.init({
|
|
6
|
+
cacheIgnore: true,
|
|
6
7
|
views: [
|
|
7
8
|
{
|
|
8
9
|
namespace: 'default',
|
|
@@ -19,11 +20,14 @@ function init() {
|
|
|
19
20
|
const els = document.querySelectorAll('[data-sortable]')
|
|
20
21
|
|
|
21
22
|
els.forEach((el) => {
|
|
23
|
+
const id = el.dataset.sortable
|
|
24
|
+
|
|
22
25
|
var sortable = Sortable.create(el, {
|
|
23
26
|
delay: 0,
|
|
24
27
|
animation: 250,
|
|
25
28
|
dragClass: 'sortable-drag',
|
|
26
29
|
easing: 'ease-in-out',
|
|
30
|
+
handle: `[data-handle="${id}"]`,
|
|
27
31
|
})
|
|
28
32
|
})
|
|
29
33
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#settings {
|
|
2
|
+
ul {
|
|
3
|
+
padding: 0;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
li {
|
|
7
|
+
width: 100%;
|
|
8
|
+
display: grid;
|
|
9
|
+
align-items: center;
|
|
10
|
+
grid-template-columns: 15% 1fr auto;
|
|
11
|
+
gap: 1rem;
|
|
12
|
+
border-bottom: 1px solid var(--pico-form-element-border-color);
|
|
13
|
+
margin-bottom: 0;
|
|
14
|
+
padding: 1rem 0;
|
|
15
|
+
|
|
16
|
+
input {
|
|
17
|
+
font-family: var(--pico-font-family-monospace);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
* {
|
|
21
|
+
margin-bottom: 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|